FreePascalFromSquareOne 5-7-2025
FreePascalFromSquareOne 5-7-2025
by Jeff Duntemann
Revision of 5/7/2025
Repeat...
Until...
This work is licensed under the Creative Commons Attribution-Share alike 3.0
United States License. To view a copy of this license, visit this Web link:
http://creativecommons.org/licenses/by-sa/3.0/us/
or send a letter to:
Creative Commons
171 Second Street, Suite 300
San Francisco CA 94105 USA
Y essir, the book you’re reading has been around the block a few times since I
began writing it in November of 1983—which I boggle to think was over forty
years ago at this revision. It’s been through four print editions and on paper sold
over 125,000 copies. It’s been translated into five languages.
Now it’s time set it free.
Pascal was not my first personal computer programming language. That honor
belongs to FORTH, though I don’t admit it very often. FORTH struck a spark in my
imagination, but it burned like a pile of shredded rubber tires, leaving a stink and
a greasy residue behind that took years to get rid of. BASIC came next, and burned
like dry pine; with fury and small explosions of insight, but there was more light
than heat, and it burned out quickly. BASIC taught me a lot, but it didn’t take me far
enough, and left me cold halfway into the night.
Pascal, when I found it, caught and burned like well-seasoned ash: Slow, deep,
hot; I learned it with a measured cadence by which one fact built on all those before
it. 1981 is a long time gone, and the fire’s still burning. I’ve learned a lot of other
languages in the meantime, including C, which burns like sticks of dynamite: It
either digs your ditch or blows you to Kingdom Come. Or both. But when something
needs doing, I always come back to Pascal.
When I began writing Pascal From Square One in the fall of 1983, I had no particular
compiler in mind. There were several, and they were all (by today’s standards)
agonizingly bad. The best of the bunch was the long-extinct Pascal/MT+, and I used
that as the host compiler for all of my example code. The book, however, was about
Pascal the language: How to get started using it; how to think in Pascal; how to see
the language from a height and then learn its component parts one by one.
When I turned the book in at the end of summer 1984, my editor at Scott,
Foresman had a radical suggestion: Rewrite the book to focus not on Pascal the
language but on Turbo Pascal the product. The maverick compiler from Scotts
Valley was rapidly plowing all the other Pascals into the soil, and he smelled a new
and ravenous audience. I took the manuscript back, and by January of 1985 I had
FreePascal from Square One, Volume 1
rewritten it heavily to take into account the numerous extensions and peccadilloes
of Borland’s Turbo Pascal compiler, version 2.0.
The book was delayed getting into print for several months, and as it happened,
Complete Turbo Pascal was not quite the first book on the shelves focusing on Turbo
Pascal. It was certainly the second, however, and it sold over 125,000 copies in the
four print editions that were published between 1985 and 1993. The Second Edition
of Complete Turbo Pascal (“2E”, as we publishing insiders called it) came out in 1986,
and addressed Turbo Pascal V3.0.
By March 1987 I had moved to Scotts Valley, California and was working for
Borland itself, creating a programmer’s magazine that eventually appeared in the fall
of 1987 as Turbo Technix. Doing the magazine was a handful, and I gladly let others
write the books for Turbo Pascal 4.0. I had the insider’s knowledge that V5.0 would
come soon enough, and change plenty, so I worked a little bit ahead, and Complete
Turbo Pascal, Third Edition was published in early 1989.
It’s tough to stay ahead in this business. Turbo Pascal 5.5 appeared in May of 1989,
largely as a response to Microsoft’s totally unexpected (but now long-forgotten)
QuickPascal. V5.5 brought with it the new dazzle of object-oriented programming,
and while I did write the V5.5 OOP Guide manual that Borland published with the V5.5
product I chose to pass on updating Complete Turbo Pascal for either V5.5 or V6.0.
The magazine Turbo Technix folded after only a year, and I left Borland at the end of
1988, wrote documentation for them until the end of 1989, and moved to Arizona
in February of 1990 to start my own publishing company. This included my own
programmers’ magazine, PC Techniques, which became Visual Developer in 1996 and
ran until early 2000.
The early 1990s saw some turbulence in the Pascal business. Borland retired
the “Turbo Pascal” brand and renamed their flagship product Borland Pascal.
The computer books division of my publisher, Scott Foresman, was engulfed and
devoured by some other publisher. Complete Turbo Pascal was put out of print, and I
got the rights back. An inquiry from Bantam Books in 1992 led to my expanding
Complete Turbo Pascal into a new book focused on Borland Pascal 7. I was able to re-
title the book Borland Pascal 7 From Square One (the title Complete Turbo Pascal had been
imposed by Scott, Foresman) and bring it up to date. That edition didn’t sell well
because by 1994, Borland had created Delphi, and most of us considered the DOS
era closed. I began writing books about Delphi and never looked back.
And so it was for almost 25 years. I considered just posting the word-processor
files from Borland Pascal from Square One back in the 90s, but figured that no one was
using Pascal all by itself anymore. Not so. I first saw FreePascal in 1998 and tried it
from time to time, but it wasn’t until January 2008 that Anthony Henry emailed me
How This Book Came to Be
to suggest that FreePascal could use a good tutorial, and it shouldn’t be too hard to
update Borland Pascal 7 from Square One to that role. He was right—and the project
has been a lot of fun.
I mention all this history because I want people to understand where FreePascal
from Square One came from, emphasizing that it’s been a work in progress for
forty-odd years. Why stop now? I don’t have to cater to publishers, paper costs,
print runs, or release schedules anymore. The book can evolve with the compiler,
and every so often you can download the latest update. My publishing company
Copperwood Press (will soon) offer a print-on-demand paper edition if you’d like
one, but you can print it yourself if you prefer. That’s how technical publishing
ought to be, and someday will be.
If...Then...Else...
11
12 FreePascal from Square One, Volume 1
Chapter 1.
The Box
that Follows a Plan
T here is a rare class of human being with a gift for aphorism, which is the ability
to capture the essence of a thing in only a few words. My old man was one; he
could speak volumes in saying things like, “Kick ass. Just don’t miss.” Lacking the
gene for aphorism, I write books—but I envy the ability all the same.
The patron aphorist of computing is Ted Nelson, the eccentric wizard who
created the perpetually almost-there Xanadu database system, and who wrote the
seminal book Computer Lib/Dream Machines. It’s a scary, wonderful work, in and out
of print for forty years but worth hunting down if you can find it. In six words, Ted
Nelson defined “computer” better than anyone else probably ever will:
A computer is a box that follows a plan.
We’ll come back to the box. For now, let’s think about plans, where they come
from, and what we do with them.
13
14 FreePascal from Square One, Volume 1
Read email.
Pay bills.
Put money into checking account if necessary.
Get cash at ATM.
Go to Home Depot.
Get Thompson’s Water Seal.
Get 2 4’ flourescent bulbs.
Get more steel wool if we’re out.
Get more #120 sandpaper if we’re out.
Get birthday present for Brian. Old West Books? He likes ghost towns.
Put together grocery list. Go to Safeway for groceries.
Sand rough spots on the deck.
Water seal the deck.
See if my back-ordered water filter cartridge is in at Sears; call first.
Replace refrigerator water filter cartridge if they have it.
Take the empty grill propane tank in and get a full one.
Do what laundry needs doing.
Go home and swim fifty laps.
That’s a lot to ask of one day. If it’s going to be done, it’s going to have to be done
smart, which is to say, in an efficient order so that as little time and energy gets lost
in the process as possible. In other words, to get the most out of a day, you’d better
have a plan.
Pay bills.
Call and add money to checking account if required based on balance.
Check in the garage for steel wool and #120 sandpaper.
Put together grocery list.
Call Desert Flower Appliances to see if the filters are in.
Check oil in the Durango. Add if necessary.
Run errands:
Go out Shea Boulevard to hit Home Depot first.
Get Thompson’s Water Seal.
Get 2 4’ flourescent bulbs.
Get more steel wool if we’re out.
Get more #120 sandpaper if we’re out.
Swap out empty propane tank.
16 FreePascal from Square One, Volume 1
Pay bills.
Replenish checking account from savings.
Put together grocery list.
Put together a Home Depot list.
Call Desert Flower Appliances to see if the filters are in.
Check in the garage for steel wool and #120 sandpaper.
Check oil in the Durango. Add if necessary.
Run errands:
Home Depot.
Old West Books.
Desert Flower Appliances.
Safeway.
Sand and water seal the deck.
If I get it, replace water filter cartridge behind refrigerator.
Do what laundry needs doing.
Read EMAIL.
Swim fifty laps.
Collapse!
Look carefully at the differences between this list and the previous list. Mostly
I’ve condensed obvious, common-sense things that I was unlikely to forget. I’ve
been to the various stores so often that I could do it asleep, so there’s really little
point in giving myself driving directions. I have an intuitive grasp of where all the
stores are located, and I can plot a minimal course among them all without really
thinking about it. At best, the order in which the stores are written down jogs my
memory in the direction of that optimal course.
I combined items that always go together. Paying bills and adding money back
into my checking account are things I always do together; to do otherwise risks
disorder and insufficient funds.
I pulled details out of the “Home Depot” item and instead added an item further
up the plan, reminding me to “Make a Home Depot list.” If I was already going to put
together a grocery list, I figured I might as well flip it over and put a hardware‑store
list on the other side. There’s a lesson here: Plan‑making is something that gets
better with practice. Every time you draft a plan, you’ll probably see some way of
improving it.
18 FreePascal from Square One, Volume 1
On the other hand, sooner or later you have to stop fooling around and execute
the plan.
I’m saying all this to get you in the habit of looking at a plan as something that
works at different levels. A good plan can be reduced to an “at-a-glance” form that
tells you the general shape of the things you want to do today, without confusing
the issue with reams of details. On the other hand, to be complete, the plan must
at some level contain all necessary details. Suppose I had sprained an ankle out in
the garage and had to have someone else run errands in my place? In that case, the
detailed plan would have been required, down to and perhaps including detailed
driving directions to the various stores.
Had Carol been the one to take over errand-running for me, I might not have had
to add a lot of detail to my list. When you live with a woman for forty-five years, you
share a lot of context and assumptions with her. But had my long-lost cousin Tony
from Wisconsin been charged with dashing around Phoenix Metro doing my day’s
errands, I would have had to write pages and pages to make sure he had enough
information to do it right.
Or if my very distant relative Heinz Duntemann from Dusseldorf were visiting, I
would have had to do all that, and write the plan in German as well.
Or...if my friend Sandron from Tau Ceti V (who neither knows nor cares what a
wood deck is, and might consider Thompson’s Water Seal a species of sports drink)
volunteered to run errands for me, I would have had to explain even the minutest
detail (over and above the English language and alphabet), including what stop lights
mean, how to drive, our decimal currency (Sandron has sixteen fingers and counts
in hex) and that cats are pets, not hors d’œrves.
To summarize: The shape of the plan—and the level of detail in the plan—
depends on who’s reading the plan, and who has to follow it. Remember this. We’ll
come back to it a little later.
I’m not going to go into a tremendous amount of detail here as to the electrical
internals of computers. For that, you might pick up my book Assembly Language Step
By Step (John Wiley & Sons, 2009; available on Amazon) and read its first several
chapters, which explain the Intel CPU family and computer memory in simple terms.
In 2014 I wrote the basic chapters in Learning Computer Architecture with Raspberry Pi,
which explain the electrical reality of ARM-based computers, and go into much
more detail. You can run FreePascal and Lazarus on the Raspberry Pi, and if you like
Pascal (or don’t feel like learning C or Python) I recommend giving them a try.
Left to her own devices, Emily does nothing but sit in one place. However, if you
take a card full of instructions and insert the card into the slot on Emily’s lid and
press the “GO” button, Emily zips off on her own, stopping and going and turning
and reversing. She’s following the instructions on the card. Eventually she reaches
and obeys the last instruction on the card, and simply stops where she is, waiting
for another card and another press of the “GO” button.
Figure 1.2 shows one of Emily’s instruction cards. The card contains thirteen
rows of eight holes. In the figure, the black rectangles represent holes punched
through the card, and the white rectangles are places where holes could be
punched, but are not. Underneath the slot in Emily’s lid is a mechanism for
detecting holes by shining eight small beams of light at the card. Where the light
goes through and strikes a photocell on the other side, there is a hole. Where the
light is blocked, there is no hole.
The holes can be punched in numerous patterns. Some of the patterns “mean
something” to Emily, in that they cause her machinery to react in a predictable
way. When Emily’s internal photocells detect the pattern that stands for “Go
forward 1 foot” her motors come on and move her forward a distance of one foot,
then stop. Similarly, when Emily detects the pattern that means “Turn right,” she
pivots on one motor, turning to the right. Patterns that don’t correspond to some
sort of action are ignored.
The Box That Follows a Plan 21
The card shown in Figure 1.2 describes a plan for Emily. It is literally a list of things
that she must do, arranged in order from top to bottom. The arrow shows which end
of the card must be inserted into Emily’s card slot, with the cut corner on the left.
Once the card is inserted into the slot in her lid, a press on her “GO” button sets her in
motion, following the plan as surely as I followed mine by jumping into the 4Runner
and heading off down Nevada Street to do my Saturday morning errands.
When Emily follows the plan outlined on the card, she moves in the pattern
shown in Figure 1.3. It’s not an especially sophisticated or useful plan—but then
again, there’s only room for thirteen instructions on the card. With a bigger
card—or more slots to put cards into—Emily could execute much longer and
more complex programs.
22 FreePascal from Square One, Volume 1
Go forward 1 foot
Go forward 10 feet
Turn left
Turn right
The Box That Follows a Plan 23
There’s no instruction to stop; when Emily runs out of instructions, she stops
automatically.
Now, Emily is a little more sophisticated than this one simple card might
indicate. Over and above the four instructions shown above, Emily “understands”
four more:
Go backward 1 foot
Go backward 10 feet
Rotate 180°
Sound buzzer
When you want to punch up a new card for Emily to follow, you must choose
from among the eight instructions in Emily’s instruction set. That’s all she knows,
and there’s no way to make her do something that isn’t part of the instruction set.
However, it isn’t always completely plain when something is or isn’t part of the
instruction set. Suppose you want Emily to move forward seven feet. There’s no
single instruction in Emily’s instruction set called “Go forward seven feet.” However,
you could put seven of the “Go forward 1 foot” instructions in a row, and Emily
24 FreePascal from Square One, Volume 1
Go forward 10 feet
Go backward 1 foot
Go backward 1 foot
Go backward 1 foot
It’s the long way around, in a sense, but the end result is indeed getting Emily to a
position seven feet ahead of her starting point. It takes a little longer, timewise, but
it only uses up four positions on the card.
This is a lot of what the skill of programming involves: Looking at the computer’s
instruction set and choosing sequences of instructions that get the job done. There
is usually more than one way to do any job you could name—and sometimes an
infinite number of ways, each with its own set of pluses and minuses. You will find,
as you develop your skills as a programmer, that it’s relatively easy to get a program
to work—and a whole lot harder to get it to work well.
PC and XT machines. Intel added a lot of computing muscle to the 8088 when it
created the 80286, but the remarkable thing was that it only added capabilities—
Intel took nothing away. The 80286’s instruction set is larger than the 8088’s, but it
also contains the 8088’s. That being the case, anything that runs on an 8088 also
runs on an 80286. This has been the case as Intel’s product line has evolved down
the years through the 80386, 80486, the Pentium, and the more recent Core. All the
instructions available in Intel’s original 8086/8088 instruction set are still there in
the latest Core CPUs. (Whether programs written for the 8088 will run on a Core
i7 really depends more on the operating system than the CPU chip.)
Emily Mark II
We can return to Emily for a more concrete example. Once we’ve played with Emily
for a few months, let’s say we dismantle her and rebuild her with more complex
circuitry to do different things. We add more sophisticated motor controls that
allow Emily to make half-turns (45°) instead of just full 90° left and right turns. This
alone allows tremendously more complex paths to be taken.
But the most intriguing addition to Emily is an electrically operated “tail” that
can be raised or lowered under program control. Attached to this tail is a felt-tip
reservoir brush, much like the ones sign painters use for large paper signs. One new
instruction in Emily’s enlarged instruction set lowers the brush so that it contacts
the ground. Another instruction raises it off the ground so that it remains an inch
or so in the air.
If we then put down large sheets of paper in the room where we test Emily, she can
draw things on the paper by lowering the brush, moving around, and then raising the
brush. Emily can draw a 1-foot square by executing the following set of instructions:
Lower brush
Go forward 1 foot
Turn right
Go forward 1 foot
Turn right
Go forward 1 foot
Turn right
Go forward 1 foot
Raise brush
The whole point I’m making here is that a computer program is a plan for action,
and the individual actions must be chosen from a limited set that the computer
understands. If the computer doesn’t understand a particular instruction, that
instruction can’t be used. Sometimes you can emulate a missing instruction by
combining existing instructions to accomplish the same results. We did this earlier
by using several instructions to make Emily act as though she had a “Move forward
seven feet” instruction. Often, however, that is difficult or impossible. How, for
example, might you make Emily perform a half-left turn by combining sequences
of her original eight instructions? Easy. You can’t.
This is one way to understand the limitations of computers. A computer has a
fundamental instruction set. This instruction set is fairly limited, and the individual
instructions are very tiny in their impact. An instruction, for example, might simply
add 1 to a location in memory. Through a great deal of cleverness, this elemental
instruction set can be expanded enormously by combining a multitude of tiny,
limited instructions into larger, more complex instructions. One such mechanism
is the subject of this book: FreePascal, as I’ll explain in the next chapter.
The Box That Follows a Plan 27
D:
CD \PARADOX3\PERSONAL
VHR MDS /N
PARADOX3
It’s not much, but it saved me having to type these four lines every time I wanted
to update someone’s phone number in my address book database. (We forget
sometimes what tremendous time savers Windows and Mac OS have been by
handling things like that for us, and retaining applications in memory for quick use
at any time.) The first command shown above moves me to the D: hard disk drive.
The second command moves me into the subdirectory containing my address book
database. The third command invokes a special utility program that resets and
clears the unusual monitor and video board that I used for years in the DOS era.
The fourth and last line invokes the Paradox 3.0 database program.
Yes, DOS batch files are ancient and little-used these days, but they perfectly
illustrate my point: Like most people’s “do-it” lists, DOS batch files run straight
through from top to bottom, in one path only.
on your grocery list. Now when you get to Safeway, suppose it’s late in the day and
the poppy‑seed rolls are long gone. You’re faced with a decision: What to buy? If
you’re having hamburgers for supper, you need something to put them on. So you
buy Mother Ersatz’ Genuine Bread-Flavored Imitation Hamburger Buns. You didn’t
write it down this way and may not even think of it this way, but your “do-it” list
contains this assumed entry:
Then again, if Mother Ersatz’ products make you feel, well...ersatz, you might
change your whole supper strategy, leave the frozen hamburg in the freezer, and
gather materials for stir-fry chicken instead:
Most of the time we perform such last-minute decision-making “on the fly,”
hence we rarely write such detailed decisions down in our original and carefully
crafted “do-it” lists. Most of the time, Safeway will have the poppy-seed rolls and we
operate under the assumption that they will always be there. We think of “Plan B”
decisions as things that happens only when the world goes ballistic on us and acts
in an unexpected fashion.
In computer programming, such decisions happen all the time, and programming
would be impossible without them. In a computer program, little or nothing can be
assumed. There’s no “usual situation” to rely on. Everything has to be explained
in full. It’s rather like the situation that would occur if I sent Sandron the alien out
shopping for me. I’d have to spell the full decision out in great detail:
marks on a piece of scratch paper for each however much of whatever I throw into
the bowl. This makes for much better cakes. (Or at least more predictable ones.)
You might write out (for cousin Heinz or perhaps Sandron the alien) this part of
the plan like so:
This is another element that you’ll see very frequently in Pascal programming.
It’s called a FOR..DO loop, and we’ll return to it later in this book.
Dig the scoop into the jelly beans and fill it.
Take the full scoop over to the digital scale.
Repeat the following:
Shake a few jelly beans from the scoop into the scale’s bowl;
Until the scale’s digital readout reads 0.25 pounds.
Exactly how many times Mary Jo has to shake the scoop over the scale depends
on what kind of a shaker she is. If she’s new on the job and cautious because she
doesn’t want to go too far, she’ll just shake one or two jelly beans at a time onto the
scale. Once she wises up, she’ll realize that going over the requested weight doesn’t
matter so much, and she’ll shake a little harder so that more jelly beans drop out of
the scoop with each shake. This way, she’ll shake a lot fewer times, and when she
The Box That Follows a Plan 31
ends up handing you half a pound of jelly beans, that’s OK—since one can’t ever
have enough broccoli-flavored jelly beans, now, can one?
Computers, of course, do mind when they go over the established weight limit,
and they don’t mind making small shakes if that’s what it takes to get the final
weight bang-on. A computer, in fact, is a tireless creature, and would probably
shake out only one bean at a time, testing the total weight after each bean falls
into the bowl.
This sort of loop is called a REPEAT..UNTIL loop, and it’s also common in
Pascal programming, as I’ll demonstrate later on in this book.
But there’s another reason: Thinking of code and data as “doing” and “knowing”
leaves out an essential component: The gizmo that does the doing and the knowing;
that is, the computer itself. I recommend that you always keep the computer in the
picture, and think of the computer, code, and data as an unbreakable love triangle.
No single corner is worth a damn without both of the other two. Between any two
corners you’ll read what those two corners, working together, create. See Figure
1.6. In summary: The program code is a series of steps. The computer takes these
steps, and in doing so manipulates the program’s data.
Let’s talk a little about the nature of data.
Let X be...
A lot of people shy away from computer programming due to math anxiety. Drop
an “X” in front of them and they panic. In truth, there’s very little math involved
in most programming, and what math there is very rarely goes beyond the high-
school level. In virtually all programs you’re likely to write for data processing
purposes (as opposed to scientific or engineering work) you’ll be doing nothing
more complex than adding, subtracting, multiplying, or (maybe) dividing—and
very rarely taking square roots.
What programming does involve is symbolic thinking. A program is a series of
steps that acts upon a group of symbols. These symbols represent some reality
existing apart from the computer program itself. It’s a little like explaining a point
in astronomy by saying, “Let this tennis ball represent the Earth, and this marble
represent the Moon...” Using the tennis ball and the marble as symbols, you can
show someone how the moon moves around the earth. Add a third symbol (a
soccer ball, perhaps) to represent the Sun, and you can explain solar eclipses and
the phases of the Moon. The tennis ball, marble, and soccer ball are placeholders
for their real-life rock-and-hot-gas counterparts. They allow us to think about the
Earth, Sun, and Moon without getting boggled by the massiveness of scale on which
the solar system operates. They’re symbols.
you’d better also imagine that you can slap a label on each bucket and write a name
on each label.
The buckets start out empty. You can, at will, put things in the buckets, or
take things out again. You might place ten marbles in a bucket labeled “Ralph,” or
removed five marbles from a bucket labeled “George.” You can look at the buckets
and compare them: Bucket Ralph now contains the same number of marbles as
bucket George—but bucket Sara contains more marbles than either of them. This
is pretty simple, and maps perfectly onto the programming notion of defining a
variable—which is nothing more than a bucket for data—and giving that variable
a name. The variable has a value, meaning only that it contains something. The
subtlety comes in when you build a set of assumptions about what data in a
variable means.
You might, for example, create a system of assumptions by which you’ll indicate
truth or falsehood. You make the following assumption: A bucket with one marble
in it represents the logical value True, whereas an empty bucket (that is, one with
no marbles in it) represents the logical value False. Note that this does not mean you
write the word “True” on one bucket’s label or “False” on another’s. You’re simply
treating the buckets as symbols of a pair of more abstract ideas.
With the above assumptions in mind, suppose you take a bucket and write
“Mike Is At Home” on its label. Then you call Mike on the phone. If Mike picks
up the phone, you drop one marble into the labeled bucket. If Mike’s phone
simply rings (or if his answering machine message greets you) you leave the
labeled bucket empty.
With the bucket labeled “Mike Is At Home” you’ve stored a little bit of
information. If you or someone else understands the fact that one marble in a bucket
indicates True, and an empty bucket indicates False, then the bucket labeled “Mike
Is At Home” can tell you something useful: That it is true that Mike is at home. Just
look in the bucket—and save yourself a phone call.
What’s important here is that we’ve put a sort of mask on that bucket. It’s not
simply a bucket for holding marbles anymore. It’s a way of representing a simple
fact in the real world that may have nothing whatsoever to do with either marbles
or buckets.
Much of the real “thinking” work of programming consists of devising these
sets of assumptions that govern the use of a group of symbols. You’ll be thinking
things like this:
In both instances, the variable itself is simply a place somewhere inside the
computer where you can store a number. What’s important is the mask of meaning
that you place in front of that variable. The variable contains a number—and that
number represents something. This is the fundamental idea that underlies all computer
data items. They’re all buckets with names—for which you provide a meaning.
Input and output are similar, and differ mainly in the direction that information
moves. Your keyboard is an input device, in that it sends data to the CPU when you
press a key. Your screen is an output device, in that it puts up on display information
sent to it by the CPU. The parallel port to which you connect your printer is another
output device, and your Ethernet network port swings both ways: It both sends and
receives data, and thus is both an input and an output device at once.
If you press the “Y” key, one road at the fork will be taken, and that fork leads to
the end of the program. If you press the “N” key, the other road at the fork will be
taken, back into the program to do some more work.
Programs like this are said to be interactive, since there’s a constant conversation
going on between the program and you, the user. Inside the computer, the program
is threading its way among a great many possible paths, choosing a path at each fork
in the road depending on questions it asks you, or else depending on the results of
the work it is doing.
The Box That Follows a Plan 37
Sooner or later, the program either finishes its work or is told by you to pack
up for the day, and it “exits,” which means that it returns control to the operating
system, whatever the operating system might be.
That’s dumb. The CPU would instead take the entire kit to the workbench and
assemble it there, only going back to the shelves for something too big to fit on the
workbench, or to find something it hadn’t anticipated needing.
One final note on our metaphor: The CPU has a number of storage locations that
are actually inside itself, closer even than RAM. These are called registers, and they
are like the pockets in a craftsmen’s apron. Rather than placing something back on
the workbench, the craftsman can tuck a small tool or part conveniently in a pocket
for very quick retrieval a moment later. The CPU has a few such pockets, and can
use them to tuck away a number or a character for a moment.
So there are actually three different types of storage in a computer: Disk storage,
RAM, and registers. Disk storage is very large (these days, trillions of bytes is not
uncommon in hard drives) and quite cheap—but the CPU has to spend considerable
time and effort getting at it. RAM is much closer to the CPU, but it is a lot more
expensive and rarely anything near as large. Few computers have more than 4 or
8 gigabytes of RAM these days. Finally, registers are the closest storage to the CPU,
and are in fact inside the CPU—but there are only a half dozen or so of them, and you
can’t just buy more and plug them in.
commands and would follow them obediently, and the net effect was a very good
education on the nature of computers.
This chapter has been groundwork, basically, for people who have had absolutely
no experience in programming. All I’ve wanted to do is present some of the
fundamental ideas of both computing and programming, so that we can now dive
in and get on with the process of creating our program metaphors in earnest.
40 FreePascal from Square One, Volume 1
Chapter 2.
The Nature of
Software development
A s I hinted in the last chapter, a computer program is a plan, written by you, that
the computer follows in order to get something done. The plan consists of a
series of steps that must be taken, and some number of decisions to be made along
the way when a fork in the road turns up.
That’s programming in the abstract, as simply put as possible. There are a lot of
ways of actually writing a program, each way focusing on a different programming
language. In this book, I’ll be talking about only one programming language, the
one called Pascal. Furthermore, I’ll be focusing on only one single “dialect” of that
language, FreePascal. FreePascal is itself very similar to the venerable Turbo Pascal
from Borland, which has been with us since 1983. Nearly all program code written
for Turbo Pascal (including Turbo Pascal’s big brother Borland Pascal) will compile
and run correctly under FreePascal.
This is not a limitation. Borland’s Pascal implementations pretty much plowed
all other Pascal dialects under the soil by 1990. If you’re going to learn Pascal
programming at all, you might as well learn the dialect comprising probably 95%
of all Pascal compilers ever sold.
Look no further. You won’t find anything better.
41
42 FreePascal from Square One, Volume 1
Assembly language
The fast answer is that computers understand something called “assembly language,”
in which each step in the plan is one of those fundamental machine instructions I
described conceptually in Chapter 1. Machine instructions are incredibly minute in
what they do; for example, a single instruction may do nothing more than fetch a
byte of data from a location in RAM and store it in a location inside the CPU called
register AX. It takes an enormous number of such instructions to do anything useful;
hundreds or thousands for small programs, and many hundreds of thousands or
even millions for major application programs like Microsoft Excel or AutoCAD.
The instructions themselves are terse, cryptic, and look more like something
copied out of a mad scientist’s notebook than anything you or I would call a
language:
MOV EAX,[EBX]
SUB EAX,EDX
AND EAX,0FF0DH
DEC ECX
LOOPNZ MSK13
Actually, the frightening truth is that even these cryptic statements are themselves
“masks” for the true machine instructions, which are nothing more than sequences
of 0’s and 1’s:
0110101000110010
0000000101110011
1110111110011011
0110110110001010
It’s possible to program computers by writing down sequences of 0’s and 1’s and
somehow cramming them into a computer through toggle switches, with an “up”
switch for a “1” and a “down” switch for a “0”. I used to do this in 1976 (for my
home-made computer called the COSMAC ELF) and thought it was great good fun,
because back then it was the best that my machine and I could do.
Is it really fun?
Mmmmm...no. In truth, it gets old in a big hurry. Doing something as simple as
making the PC’s speaker beep requires fifteen or twenty machine instructions, all
laid out precisely the right way. Even writing assembly language in almost-words like
MOV EAX,[EBX] is tiresome and done today only by the most curious and the most
dedicated among us. But the computer only understands machine instructions.
What to do?
The Nature of Software Development 43
Remainder := Remainder - 1;
IF Remainder = 0 THEN
BEGIN
BEEP;
Write(‘Warning! Your time has run out!’);
END;
Once you have a feeling for the high-level language, you can look at sequences
like this and know exactly what they’ll do without stretching your brain too
much. The reality is that it may take hundreds of machine instructions for the
CPU to do the work involved in what we have written, but for our eyes, it’s only a
few short lines.
like words and phrases, and writes out an equivalent file of machine instructions.
This file of machine instructions can be loaded and executed by the CPU. But even
though this program file consists of thousands or tens of thousands of machine
instructions, we never had to know even a single machine instruction to write it. All we had
to do was understand how the English-like commands of the high-level language
affect the machine. The compiler takes care of the “ugly” stuff like remembering
which sequence of twenty machine instructions beeps the speaker. All we have to
remember is what BEEP does.
Much better!
A compiler program is thus a program that writes other programs, with some
direction from us. It does its job so well that we can actually forget all about what
happens with the machine instructions (most of the time, anyway) and concentrate
on the logic of how the English-like words and phrases go together.
Different languages
A host of high-level languages exists for the PC. Pascal, C, C++, C#, Python, Javascript,
and BASIC are the most common, but there are hundreds of others with obscure or
puckish names like COBOL, Perl, Java, Forth, APL, Smalltalk, Eiffel, PHP, PL/1, Rexx,
FORTRAN, Lisp, Scheme, and on and on and on. As different as they may seem on
the surface, they all do the same thing underneath: Arrange machine instructions to
accomplish the work that we encode in English-like words and phrases.
Pascal, for example, uses the word Write to display information on the screen.
BASIC, by contrast, uses the word PRINT. The C and C++ languages use the word
printf. Forth uses the word TYPE. Others use words like SAY, OUTPUT, or
Show. Those words were chosen by the people who designed each language for
reasons they considered good ones. However, what those different words do is all
pretty much the same.
A high-level language is defined as a set of commands, plus a set of rules as to
how these commands are used alone and combined. These rules are called the syntax
of the language, and they correspond roughly to the syntax and grammar of spoken
human languages like English and German. Computer languages are not as rich in
expression as human languages, but they are much more precise—and needless to
say, they “speak” only of things that a computer can actually accomplish.
on a theme called dialects. Each person or company who creates a compiler that
understands a given computer language might construct the compiler to understand
things a little differently from other compilers written earlier for the same language.
Thus not all Pascal compilers agree on what certain program commands mean.
Nor do all BASIC compilers agree on the syntax and command set of BASIC. If you
write a program in Drs. Kemeny & Kurtz’s TrueBasic it won’t necessarily compile
correctly if you hand that same program to Microsoft’s Visual BASIC.
Dialects usually happen when companies who write compilers add new features
and abilities to languages, in order to produce a more powerful language or (at least)
one perceived as different from the compilers already on the market.
FreePascal is a dialect of the Pascal language. Pascal has been around for almost
fifty years now (since 1971) and it’s done some serious growing in the process.
Pascal predates PCs; in fact, it predates all microcomputers of any design and was
originally created to run on massive mainframe computers, those famous for
being kept behind locked doors in air-conditioned rooms with raised floors. The
man who designed Pascal, Niklaus Wirth, was trying to prove a point in computer
science when he designed Pascal, and really didn’t intend to write an exhaustive
and generally useful language. Other companies added features to Pascal over the
years, and little by little the language broke into mutually-exclusive dialects that
were about 90% common. Alas, in computer languages as in horseshoes, “almost”
just doesn’t count.
For example, Wirth’s original Pascal wrote information to disk files with the Put
command. Borland’s Turbo Pascal broke with this concept in the early 1980s by using
the Write command to write data to disk, and omitted the Put command completely.
FreePascal does things the Turbo/Borland Pascal way. If you take a program written
in the original version of Pascal and try to compile it using FreePascal, the compiler
will display an error message if it encounters a Put command, since it doesn’t know
what sequence of machine instructions Put is supposed to represent.
This problem of dialects is worse in most languages other than Pascal, because
FreePascal’s progenitor Turbo Pascal has dominated the Pascal world for so long that
most of the earlier dialects have simply disappeared. If you ever attempt to program
in different versions of BASIC, on the other hand, the dialects problem will appear
in spades, and you will have a great deal of work to do making programs written for
one BASIC compile correctly using another BASIC.
In general, this book will be speaking of the FreePascal dialect of Pascal. (FreePascal
speaks several, as I’ll describe later on.) Here and there, I’ll be pointing out differences
between FreePascal and other Pascals, but as I’ve said before, the differences are
becoming less and less important as time goes on.
46 FreePascal from Square One, Volume 1
A fifth grader once wrote on an exam: “Now that dinosaurs are safely dead, we
can call them clumsy and stupid.” It’s easy to dismiss the DOS-style command-
line interface as clumsy, awkward, and time consuming, but it has the virtue of
simplicity. (We also forget that dinosaurs ruled the Earth for 100 million years. They
must have been doing something right.) It’s also far from extinct.
The window in which the command prompt appears is generally called a console
window. Figure 2.1 is a typical console window as displayed by Windows 2000 and
after. The desktop versions of Linux and Unix also have console windows. Mac
OS/X has a console window called Terminal. All console windows on whatever
operating systems work and look pretty much the same. (The group of commands
that each understands is different, of course.)
The existence of console windows make certain things easier for people creating
computer language compilers like FreePascal. If a compiler only communicates
with its users through simple text screens, it can work pretty much the same way
48 FreePascal from Square One, Volume 1
no matter what operating system is in control of the computer. So it was done with
FreePascal: By default, it works in a console window. This means that you can open
a console window and type textual commands to FreePascal, which responds by
displaying textual information in the same window on the next line or lines. Figure
2.2 is a console window “conversation” with FreePascal, as it would occur during
the compilation of a very simple pascal program.
All the major FreePascal implementations look pretty much the same when run
in console windows, whether you’re using Windows, Linux, OS/X, or anything else.
Of course, once you begin writing programs that work specifically with a graphical
user interface belonging to a particular operating system, the explanations begin to
diverge. I won’t, however, be going that far in this introductory volume.
Abilities like these allow us to create simple editing and compiling environments
entirely in text mode, to run in a console window. Many of the concepts we’re
used to in graphical operating environments like Gnome, KDE, and Windows
can be emulated, albeit on a character basis and with far less resolution. Such an
environment (whether implemented in text-mode or graphics mode) is called an
Integrated Development Environment (IDE), and one is included with and installed with
FreePascal on most operating systems for which the compiler is available. The
FreePascal console window IDE is shown in Figure 2.3, with a simple Pascal program
loaded and displayed, ready to be compiled and run.
programs I’ll be presenting in this book, but when you’ve learned enough Pascal
to move up to object-oriented programming and the creation of windowed
graphical applications, you’ll find that Lazarus makes the process hugely easier.
That’s why I want to use Lazarus as the teaching IDE from the very beginning.
The skills you develop now by using Lazarus to write simple programs will make
learning the rest of it a snap later on when you need it.
The Nature of Software Development 51
Lazarus is itself a multi-window application, and the good news is that we can
make Lazarus a lot simpler to understand by closing the windows that you don’t
need for the time being. Figure 2.5 shows Lazarus as it appears while you’re writing
a simple Pascal program. The program code is edited from a GUI window (here,
under Windows XP) but the program’s output is displayed in a console window.
Figure 2.5. Lazarus Used to Edit and Run a Simple Pascal Program
There are four Lazarus windows shown: The control window is the narrow one
at the top. It’s the “boss” window that controls all the rest of the Lazarus system.
The larger window in the center is the editor window, where you write your
Pascal code and fix your inevitable errors. The slim window at the bottom is the
messages window, which tells you what the system is doing and points out your
errors. The black window is a console window, and is not itself a part of Lazarus.
Lazarus launches a console window when your program runs, to provide a separate
“blackboard” (literally!) on which your program can write its output, and display
the input that you enter through the keyboard.
52 FreePascal from Square One, Volume 1
images that must be “baked into” an executable program file. Finally, there are files
associated with a programming project that summarize the details of the project
for the benefit of the programming environment itself. In our case, that would be
Lazarus, but all of the ambitious RAD environments used today generate project files
to manage the mountain of details inherent in advanced programming projects.
You may not have to pay details attention to resource and project files while writing
simple programs in FreePascal, but at least understand that they’re necessary. For
the most part they are generated and maintained by the RAD environment, and
while they must be understood, you don’t often have to explicitly edit them.
In your early explorations of FreePascal, you’ll be creating simple, standalone
.PAS files and compiling them to executable files. (I’ll explain about creating unit
files for maintaining reusable code libraries later on.) You’ll then run the executable
files in a console window to see how well they work. The executable files may be run
from a console window prompt without any help from the FreePascal compiler or
the Lazarus IDE. However, for the example programs in this book you’ll generally
be able to test your executable files from inside console windows created by the
Lazarus IDE itself.
Cross-platform programming
This is a slightly advanced topic, but it’s worth noting as part of the big picture:
FreePascal can handle cross-platform programming. What this means is that you
create a program on one operating system running on one type of computer
hardware, but generate programs that can run on other, different operating systems
or hardware. For example, if your main machine is Microsoft Windows running on
Intel x86 hardware (a very common combo that we often call the “Wintel” platform)
you can still write programs that will run under Linux, Unix, Mac OS/X, and a long
list of other operating systems that you may not have even heard of.
There are often limitations on what such programs can do. For example, there
are whole classes of operating systems that don’t have a graphical user interface.
Everything that they do is done in a purely textual environment much like a console
window. If you load a graphical windowed program intended for the Linux GNOME
windowing environment on such a system, the machine will throw up its hands
in despair because it won’t know how to deal with requests to create windows or
accept mouse clicks to buttons.
The issues that come into play during cross-platform programming are many
and they are subtle. You need to become fluent in FreePascal long before you should
attempt to create code on one platform to run on another.
54 FreePascal from Square One, Volume 1
FreePascal and Lazarus contain a number of built-in tools to help you flush out
the inevitable bugs you’ll find in your programs. Using these tools and your own
good sense, you gradually find and fix the causes of whatever bugs come to light.
This process can take awhile. Getting rid of disastrous and obvious bugs happens
early in the cycle, because you’re pretty motivated to find them and fix them. Getting
rid of minor or subtler bugs that don’t necessarily make your program worthless
could be a long process—and for programs of a useful size may be an endless one.
People say that there’s always one more bug, and you can devote as much time and
energy as you care to rooting that last bug out.
But even when you root the “last” bug out, there’s always one more bug. Trust me.
PROGRAM EatAtJoes;
USES Crt;
BEGIN
ClrScr
GotoXY(10,5);
Writeln(‘Eat at Joe’’s!’);
GotoXY(10,7);
Writeln(‘Ten Million Flies Can’’t ALL Be Wrong!’);
Readln;
END.
like BEGIN and END should be in uppercase only. The default in Lazarus is all
lowercase. So in any code that Lazarus generates automatically (like the skeleton
program here, and much else in more complex programs) reserved words will be
in lower case. This is a bad idea. Although you can do it whichever way you like,
keep this in mind: Reserved words are different. They have “super powers” and must be
treated specially. It pays to set them off somehow from ordinary program identifiers
like constants and variables, to make your program code more readable and less
vulnerable to certain types of completely avoidable errors.
Some people will try to tell you in a weirdly excitable tone: “But...but...uppercase
letters mean that you’re...shouting!”
Maybe somewhere in the world they do, somewhere ancient and obsolete but
alas, not yet dead. In Pascal, reserved words are the framing members of your
programs. They must stand out. If you put them in lowercase, you will miss them now
and then and make stupid mistakes, and spend more time and energy fixing things
than you otherwise would.
Fortunately, there’s a way to configure Lazarus to keep reserved words in
uppercase. Go to Tools|Options|Codetools|Words. Under Keyword Policies you
can select one from several radio buttons. Click UPPERCASE and then OK. Now,
when Lazarus uses a reserved word it will be in uppercase.
I’ll speak in more detail of reserved words in a later chapter.
smart, but it has limitations. One of these is that it will point out an error not where
the error actually is, but where the compiler first noticed that there was an error.
It’s a little like the situation you find yourself in when you go out after supper to
fill your gas tank and forget your wallet. You made a mistake when you failed to put
your wallet back in your pocket when you changed pants after work. But you only
notice the mistake when you’ve pumped a tank full of gas and reach for your credit
cards. Whoops...
I make the point here because a lot of people assume that FreePascal not only
discovers errors but points out where those errors are. Not true—it can only tell you
where it first noticed that something was wrong. Keep that in mind as you struggle
through your first few error-rich sessions with FreePascal!
Repairing this particular error is easy. Place the text cursor at the end of the
ClrScr line, and type a semicolon there. Click the Save icon (the diskette) to save
the change. Then select Run | Build again.
The Nature of Software Development 63
Or, if you think there may be still more compile-time errors lurking in your
Pascal code, select Run | Quick Compile. This option is different from Build in
that it only compiles the source code file currently loaded in the Source Editor
window. When you’re working on a large, multi-file project, Build will take
more time. If you’re simply checking the current source code file for errors, Run
|Quick Compile will do the job faster. (For small files like this you won’t see
much difference.)
Lazarus doesn’t blow a trumpet or pop up a special window when a build
completes successfully. All you’ll see is the following message in the Messages
window, in very small type:
Project “EatAtJoes” successfully built
All this means is that Lazarus used with “-B” command-line option when it
invoked FreePascal to build your project. The -B (Build) option creates a “clean”
compile in which all Pascal files in the project are compiled fresh, and no object-code
files generated earlier are used. There’s only one Pascal file in this very simple project,
but in more complex projects you have the option of compiling only those Pascal
files that have changed since the last time you compiled them. This can save you a
considerable amount of time on very large projects with many source code files.
working directory (or wherever you saved the EatAtJoes project) you’ll find a file
called EatAtJoes.exe. This is a complete and independent executable file, and
you can run it by naming it on a console window command line, or by double-
clicking on it in Windows Explorer. (Other graphical environments may have their
own ways of executing standalone executable files.)
An interesting point to be made here is that FreePascal, Lazarus, and the EatAtJoes
program you’ve just successfully run are all native-code programs, conceptually
identical and pretty much equal in the eyes of the computer itself. The programs
you’re writing are not “toy” programs by nature nor are they crippled in any way.
(They’re just small—for now.) You could theoretically write something as complex
as—or even more complex than—FreePascal itself. This is definitely big-time
programming. Don’t let anyone ever tell you otherwise.
When the program that you ran finishes its execution, it immediately hands the
baton back to the Lazarus environment. You can then continue the development
cycle of write code, save, build, and run.
67
68 FreePascal from Square One, Volume 1
And I’ll never forget the horrible sinking feeling in my guts the first time I saw
a Xerox copier with its panels off, merrily making copies. There were gears and
drums turning, cams flipping, relays clicking, little claws grabbing a document and
dragging it through a maze of harsh green fluorescent lights, past crackling high-
voltage corotron wires, under a grimy, grinding developer housing, and ultimately
dropping it into a stainless-steel paper chute.
The instructor must have seen the expression on my face. He snorted through
his bushy mustache and said, “Hey, Jeff, don’t panic! It’s only a copier. Just take it
subsystem by subsystem.”
He was right, of course. It’s easy to fall into despair the first time you try to make
sense of a programming language. There’s five times the complexity of that gross
little Model 660 electromechanical copier, and each and every detail must be exactly
right, or nothing is accomplished but the wholesale tearing of hair.
So do what I did, and take it subsystem by subsystem.
BEGIN
ClrScr;
GotoXY(15,11);
Writeln(‘Eat at Joe’’s!’);
GotoXY(15,12);
Writeln(‘Ten Million Flies Can’’t ALL Be Wrong!’);
Readln;
END.
BEGIN and END are what we call reserved words. They have special meanings to
the FreePascal compiler and you can’t use them for anything else. I’ll have more
to say about reserved words a little later, when we get into the detailed view of the
Pascal language. They are the “framing members” of a Pascal program—the logical
2 X 4s that give a program its structure. They define its shape, and control the way
execution flows within a program.
In this book, reserved words will always be printed entirely in upper-case
characters, as BEGIN, END, WHILE, RECORD, and so on. (Additionally, all
program identifiers of any kind will be printed in bold in the body text of the book.) In
Pascal, character case is not significant for reserved words and other identifiers like the
names of variables, so some people write them in lower case. That works; FreePascal
considers BEGIN and begin to be precisely the same. I like to place reserved words
in capital letters so that they stand out—it helps you take in the overall structure of a
program quickly and easily. Reserved words and variables are not the same thing—
not even close, in fact—so making them look different is actually very useful.
70 FreePascal from Square One, Volume 1
Statements
Between BEGIN and END are six lines, each of which is a step in the program’s
execution. When a program begins running, the first step in the main block executes,
(here, ClrScr) and then the second, and then the third, and so on, until the last step
in the main block is executed. Program execution is then finished, and the program
stops running.
Each one of those steps is called a statement. The single word ClrScr is a statement,
as is the more complicated line Writeln('Eat at Joe''s!').
Note carefully here that statements and lines are very different things. (Beginners often
confuse them, with predictably bad results.) A statement may exist all by itself on a
single line. A statement may occupy more than one line. More than one statement
may exist on a single line. The key is that statements are separated by semicolons.
Semicolons don’t end lines. They separate statements. This means that you can have
a perfectly legal line like the following:
ClrScr; GotoXY(15,11);
Here there are two statements on one line, with a semicolon after the first to act as a
separator. There is a semicolon after the second statement as well, but that semicolon
separates the statement GotoXY(15,11) from whatever statement begins the next
line. Whether you should place multiple statements on the same line is a question of
readability, not of program correctness. The FreePascal compiler does not recognize lines.
In other words, multiple statements on the same source code line compile to the
very same object code as do multiple statements each on its own source code line.
More on this a little later.
The statements in EatAtJoes.pas are simple and easy to dope out by reading
them and by watching what the program does. ClrScr clears the screen. GotoXY
moves the cursor to an X,Y position on the screen. Think of your CRT screen as a
Cartesian grid like the ones you worked with in high school math. The X (across)
value comes first, followed by the Y (down) value. The upper left corner of the
screen is the origin, 1,1. Saying GotoXY(15,11) moves the cursor 15 positions
across, and 11 positions down. Writeln writes a line of text to the screen, starting
at the cursor position.
There is always a period after the END of the main block of a Pascal program.
The period indicates that the fat lady has indeed sung, and that the program is over.
Control leaves the Pascal program and returns to the operating system.
The Secret Word Is "Structure" 71
Compound statements
I’ll have a great deal more to say about statements later on in this book. It’s
important to note that, in a Pascal sense, the whole main block of the program is
itself a compound statement. In most cases, a compound statement is some number of
statements delimited by a BEGIN and END reserved word. There are a couple of
instances where a compound statement may be framed by other reserved words,
like REPEAT and UNTIL rather than BEGIN and END. We’ll deal with these
special cases later on.
It might help to characterize the main block by considering it to be a collective
statement that indicates the larger, single purpose that the program as a whole was
designed to accomplish. Just as a sentence in the English language is a statement
made of words followed by a period, so the main block in Pascal is a compound
statement made of statements followed by a period. This compound statement
summarizes the program’s larger purpose, and by reading the main block of a Pascal
program first, you should be able to work out the big picture of what the program
is supposed to do.
Compound statements appear in many other parts of the Pascal language.
You’ll be tripping over them wherever you go. When you see one in a program,
ask yourself what the unifying purpose of the compound statement is. It’ll help
you refine your “structure vision” and help you focus on just that one part of the
program as a whole.
1 {--------------------------------------------------------------}
2 { Aliens }
3 { }
4 { by Jeff Duntemann }
5 { FreePascal V2.2.0 }
6 { Last update 2/8/2008 }
7 { }
8 { From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
9 {--------------------------------------------------------------}
10
11 PROGRAM Aliens;
12
13 USES Crt; { For ClrScr, Random, and Randomize }
14
15 CONST
16 MaxLength = 9; { The longest name we’ll try to generate }
17 MinLength = 2; { The shortest name we’ll try to generate }
18 LastLetter = 122; { Lower-case ‘z’ in the ASCII symbol set }
19
20 TYPE
21 NameString = STRING[MaxLength]; { Waste no space in our strings! }
22
23 VAR
24 Printables : SET OF Char; { Holds the set of printable letters }
25 I,J : Integer; { General-purpose counter variables }
26 NameLength : Integer; { Holds the length of each name }
27 NameChar : Char; { Holds a randomly-selected character }
28 NamesWanted : Integer; { Holds the number of names we want }
29 CurrentName : NameString; { Holds the name we’re working on }
30
31 BEGIN
32 Randomize; { Seed the random number generator }
33 Printables := [‘A’..’Z’,’a’..’z’]; { Only printable letters! }
34 ClrScr;
35 Write(‘How many alien names do you want? (1-10): ‘);
36 Readln(NamesWanted); { Answer the question }
37
38 FOR I := 1 TO NamesWanted DO
39 BEGIN
40 CurrentName := ‘’; { Start with an empty name }
41
42 REPEAT
43 NameLength := Random(MaxLength+1); { Pick length for this name
}
44 UNTIL NameLength >= MinLength;
45
46 FOR J := 1 TO NameLength DO { Pick a letter: }
47 BEGIN
48 REPEAT { Keep picking letters until one is printable: }
The Secret Word Is "Structure" 73
49 NameChar := Chr(Random(LastLetter));
50 UNTIL NameChar IN Printables;
51 CurrentName := CurrentName + NameChar; { Add to the name }
52 END;
53 Writeln(CurrentName); { Finally, display the completed name }
54 END;
55 Readln; { Pause until Enter hit so you can see the names }
56 END.
Don’t panic!
I mean that. You’re not going to be tested on the full details of how the Aliens
program works at this point, so don’t worry about digesting it whole and in every
last little detail. It’s a complete Pascal program that even does something interesting.
I’ll be talking about it for a while in this chapter, explaining most of its workings as
I do. So follow along as we go, and don’t fret not knowing the details of character
sets or the REPEAT..UNTIL statement right now. All in good time. Remember,
we’re going for the big picture here. The details will crystallize out in the chapters
to come.
The data definitions in Aliens.pas are shown by themselves on the next page:
CONST
MaxLength = 9; { The longest name we’ll try to generate }
MinLength = 2; { The shortest name we’ll try to generate }
LastLetter = 122; { Lower-case ‘z’ in the ASCII symbol set }
TYPE
NameString = STRING[MaxLength]; { Waste no space in our strings! }
VAR
Printables : SET of Char; { Holds the set of printable letters }
I,J : Integer; { General-purpose counter variables }
NameLength : Integer; { Holds the length of each name }
NameChar : Char; { Holds a randomly-selected character }
NamesWanted : Integer; { Holds the number of names we want }
CurrentName : NameString; { Holds the name we’re working on }
The data definition part of a Pascal program is almost literally a set of blueprints
for whatever data the program will be using during its execution. The Pascal compiler
reads the definitions and sets up a little reference table for itself that it uses while it
converts your source code file to an object code file. This reference table allows the
FreePascal compiler to tell you when something you’re trying to write as part of a
program is bad practice or nonsensical.
Variables as buckets
Variables are defined after the VAR reserved word. Think of variables as buckets
into which data values may be placed. In variable definitions, you declare the name
of a variable followed by its type. The name and the type are separated by a colon.
Variables—like buckets—come in a great many shapes and sizes. The type of a
variable indicates how large the bucket is and what sorts of stuff you can safely put
in it. A plastic water bucket will carry water handily—but don’t try to lug molten
lead in it. A colander can be thought of as a bucket suitable for carrying meatballs—
but don’t expect to use it to hold flour or tomato juice without making a mess.
The notion of types in Pascal exists precisely to keep you from making certain
kinds of messes.
In Aliens, there’s a variable called NameLength. Its type is Integer, which is
a signed whole number from -32,768 to 32,767. NameLength is thus a bucket
for carrying numeric values falling in that range that don’t have a decimal part.
Similarly, NameChar is a Char variable, meaning it is intended to hold character
values like ‘A’ or ‘*’.
The Secret Word Is "Structure" 75
This is a type definition. It defines a type called NameString. This type is a string type—
meaning that it’s designed to contain data in alphanumeric strings of characters like
‘I am an American, Chicago‑born’ or ‘THX1138’. FreePascal strings may be from 1
character to 255 characters long and you can create a string type in any size within
that range. That’s what the type definition statement in Aliens does: It creates a
string type with a length given by a constant named MaxLength. (We’ll get back to
MaxLength and what it is shortly. For now, just assume it’s a number—or look a
few lines back to see how it’s defined!)
NameString is a very simple type. You can create much, much more complex
types in FreePascal, as I’ll explain later in this book. It’s easy to see how a type is in
fact a blueprint for making buckets, in that it defines what sort of data some new
kind of bucket is meant to contain.
Then, when you actually need a bucket of this new type, you can make one in the
VAR section:
This statement gives you a variable in memory that contains string data. The name
of the variable is CurrentName. The length of the string is given by the MaxLength
constant.
Remember: A type is not a variable and holds no data. It’s simply a spec or
template that allows you to create variables with a specified size and set of attributes
and uses. Use the TYPE reserved word to create blueprints for buckets—and then
use VAR to create the buckets themselves.
76 FreePascal from Square One, Volume 1
values are not predictable, and can cause many kinds of trouble. Better by far to give
every variable a value early on, before it comes upon one by accident!
You can give a variable a value in several ways, but the most straightforward way
is through an assignment statement. An assignment statement takes a value and assigns
that value to a variable. In effect, it takes the value and “loads it into” that variable, as
though dropping something into a bucket. Here’s a simple assignment statement:
Repetitions := 141;
What we’ve done here is taken the numeric value 141 and dropped it into the
variable Repetitions. The two character sequence := is called the assignment operator.
When FreePascal sees the assignment operator, it takes whatever is on the right side
of the operator and drops it into whatever is on the left side.
Here’s an assignment statement from Aliens.pas:
CurrentName := '';
What it does is drop an empty string (that is, a string containing no characters) into
the variable CurrentName. (A string with something in it would look like this:
‘Jeff’) It may seem odd to think of dropping an empty value into a bucket, so you
might consider this a way of emptying the bucket of anything else that might have
already been there.
Taken as the sum of its individual steps, brushing your teeth is a real mouthful.
When you actually get down and do it, you run faithfully through each of those
steps, and if you ever had to tell somebody how to do it, you could. But when you’re
trying to impose some order on your morning, the whole shebang shows up in your
mind under the single descriptive term, “brushing your teeth.”
behind a single identifier of your own choosing. You can execute the new identifier
as though it were a single statement, masking the complexity represented by the
original sequence of Pascal statements.
Suppose you have these three statements in a Pascal program:
DoThis;
DoThat;
DoTOther;
Taken together, these three statements accomplish something. Let’s call that
something “grobbling.” (It’s a made-up word.) We could say that the following
compound statement represents what must be done in order to grobble:
BEGIN
DoThis;
DoThat;
DoTOther;
END;
We can hide this compound statement behind a single statement, which we’ll
call Grobble, so that any time we need to execute those three statements together,
we only have to execute this single statement:
Grobble;
Doing it is fairly easy. We mostly need to give a name to the compound statement
shown above:
PROCEDURE Grobble;
BEGIN
DoThis;
DoThat;
DoTOther;
END;
An adventure in structuring
To make it all click, let’s do it, right now, to Aliens.pas. This is a slightly advanced
exercise, and if you’ve never written a line of program code before, some of it may
puzzle you. Bear with me—and have faith that all will be explained in good time.
80 FreePascal from Square One, Volume 1
One of the things that Aliens has to do is choose a length for any given alien name.
It does this by “pulling” random numbers repeatedly until it pulls a random number
within a specified range. This range is the range from MinLength to MaxLength,
both of which are defined as constants, and in this case are equivalent to the range
2 through 9.
The code that pulls a random length within those two boundaries is this:
REPEAT
NameLength := Random(MaxLength+1); { Pick a length for this name }
UNTIL NameLength >= MinLength;
Creating a function
Random numbers are useful things, and it’s even more useful to be able to specify
a range of values within which a random number is to be pulled. It would be nice
to have a procedure of some sort that would pull a random number for us without
our having to remember all the precise details of how it was done. We could call the
procedure Pull, and it would be a sort of number-generating machine. We would
pass it a minimum value and a maximum value, and Pull would somehow return a
value for us that fell within those two bounds.
As I said a little earlier: In Pascal, a function is a procedure that returns a value.
I’ll have much more to say about functions and how they’re used later in this book,
but it cooks down to that. To illustrate, suppose we had a numeric variable called
NumberBucket. We could define a function called Pull, and it would be Pull’s job
to generate a random number. To fill NumberBucket with a brand-new random
number, we would use this statement:
The Secret Word Is "Structure" 81
NumberBucket := Pull;
Although it may look like one, Pull is neither a constant nor a variable. It’s actually a
compound statement masquerading as a single value. The value is computed within
the compound statement, and then returned “through” the name of the function.
With that in mind, let’s create the Pull function from the three lines in Aliens
that pull a random length for alien names:
VAR I : Integer;
BEGIN
REPEAT
I := Random(MaxValue+1); { Pick a length for this name }
UNTIL I >= MinValue;
Pull := I;
END;
Things have changed a little—but for a reason, as I’ll explain. The central portion of
Pull does the same thing that the three lines we lifted from the Aliens program did.
What we’ve mostly added is framework; a body for the lines to exist in.
But we’ve also added a strong measure of generality. The lines that pull a random
name length within Aliens can be used only to pull a random name length. The new
Pull function can be used to pull random numbers within a specified range for any
reason. We could use it just as easily in a dice game as in an alien name generator—
and that’s a big, big advantage.
We can use Pull in Aliens.pas. Just insert the definition for Pull in the
program file just before the beginning of the main block, and then replace the three
lines that pull a random name length with the following single line:
NameLength := Pull(MinLength,MaxLength);
The two items MinLength and MaxLength are called parameters. They’re special-
purpose variables belonging to the function. They work as pipelines, allowing you
to drop values into the function. Drop a “7” value into MaxLength, and the Pull
function’s internal machinery will receive the value 6 (1 less than MaxLength) as
the maximum allowable value for the random number it’s been told to generate.
The altered copy of Aliens.pas (let’s call it Aliens2.pas) is shown on
the next page, with the Pull function replacing the three lines used to pull random
numbers in the original Aliens.pas.
82 FreePascal from Square One, Volume 1
1 {--------------------------------------------------------------}
2 { Aliens2 }
3 { }
4 { by Jeff Duntemann }
5 { FreePascal V2.2.0 }
6 { Last update 2/8/2008 }
7 { }
8 { From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
9 {--------------------------------------------------------------}
10
11
12 PROGRAM Aliens2;
13
14 USES Crt; { For ClrScr, Random, and Randomize }
15
16 CONST
17 MaxLength = 9; { The longest name we’ll try to generate }
18 MinLength = 2; { The shortest name we’ll try to generate }
19 LastLetter = 122; { Lower-case ‘z’ in the ASCII symbol set }
20
21 TYPE
22 NameString = STRING[MaxLength]; { Waste no space in our strings! }
23
24 VAR
25 Printables : SET OF Char; { Holds the set of printable letters }
26 I,J : Integer; { General-purpose counter variables }
27 NameLength : Integer; { Holds the length of each name }
28 NameChar : Char; { Holds a randomly-selected character }
29 NamesWanted : Integer; { Holds the number of names we want }
30 CurrentName : NameString; { Holds the name we’re working on }
31
32 FUNCTION Pull(MinValue,MaxValue : Integer) : Integer;
33
34 VAR I : Integer;
35
36 BEGIN
37 REPEAT
38 I := Random(MaxValue+1); { Pick a length for this name }
39 UNTIL I >= MinValue;
40 Pull := I;
41 END;
42
43
44 BEGIN
45 Printables := [‘A’..’Z’,’a’..’z’];
46 Randomize; { Seed the random number generator }
47 ClrScr;
48
49 Write(‘How many alien names do you want? (1-10): ‘);
The Secret Word Is "Structure" 83
Hiding complexity
The details of having to pull a number again and again until one appears that falls
within a specified range are masked now. We only see the “front door” of the random
number factory. The machinery that actually builds the random numbers is hidden
away behind the door somewhere. And that’s good, because most of the time we
really don’t care how random numbers are made; we only care that they do get made,
and made according to our specifications.
It’s true that creating the Pull function added a few lines to the program. Later
on, we’ll see how we can remove Pull from Aliens2.pas and place it in a library
of functions and procedures called a unit. This library is available to any of your
programs that need it. If ten of your programs need a random-number puller, you
can give a random number puller to all ten of them—and yet have only one copy
of Pull’s ten lines of code. If you yank enough general-purpose procedures and
functions out of your programs into libraries, you can actually cut the source code
bulk of your programs considerably.
Now, consider a simple accounting program. A good one (even a simple one,
as accounting programs go) might have 10,000 lines of code. You could write all
10,000 lines of code in one enormous main block. But how would you read and
understand those 10,000 lines of code? You’d probably have to do what was done
in the ancient days of programming, and literally cut a long program listing up into
chunks with a scissors, and only look at the chunk you needed to concentrate on at
the moment.
Even War and Peace is divided into chapters. Procedures are often used as “chapters”
in a larger program. In our accounting program example, you would have several
accounting tasks like Payroll, Accounts Payable, Accounts Receivable, General Ledger,
and so on. It’s possible to make each of these accounting tasks a procedure:
PROCEDURE AccountsPayable;
BEGIN
<about 2000 lines>
END;
PROCEDURE AccountsReceivable;
BEGIN
<about 3000 lines>
END;
PROCEDURE Payroll;
BEGIN
<about 2000 lines>
END;
PROCEDURE GeneralLedger;
BEGIN
<about 2200 lines>
END;
Now at least you have a fighting chance. When you need to work on the payroll
portion of your accounting program, you can print out the Payroll procedure and
ignore the rest. All the “payroll‑ness” of the accounting program is concentrated
right there in one procedure, so you don’t have to go searching for payroll details
across the entire program. Better still, details that you don’t have to pay attention to
right now remain hidden, inside their respective procedures.
The Secret Word Is "Structure" 85
With your payroll blinders on, you’ll have a much easier time focusing on the
payroll part of the program. The main block becomes quite small then, and is
mostly a little menu manager that lets you choose which of the four big procedures
you want to run.
I should note here that “using” Lazarus in this book does not
include using its GUI builder. Explaining that will require a full
treatment of object-oriented programming and software com-
ponents, which is outside the scope of this introductory book.
If...Then...Else...
89
90 FreePascal from Square One, Volume 1
Chapter 4.
Installing FreePascal
and Lazarus
O ne of the (few) downsides to free and open-source software is that you can’t
cover installation by saying, “Order the product, open the box, and follow the
manufacturer’s instructions.” It’s a little more complex than that, especially when the
product (like FreePascal) can be run on a surprising number of different operating
systems and CPU types. So in this chapter, I’ll explain how to find the downloadable
install suites for FreePascal, how to be sure you have the right one, and then how to
install it for Windows and Linux.
https://sourceforge.net/projects/freepascal/files/
91
92 FreePascal from Square One, Volume 1
https://www.lazarus-ide.org/
FreePascal’s Documentation
FreePascal has a substantial set of manuals distributed as electronic files. The manuals
may be downloaded as individual files, or they are available online in HTML format,
where you can read them with an ordinary Web browser.
The available manuals for FreePascal are these:
• The FreePascal User’s Guide (user.pdf) is a concise description of the Pascal
language as implemented by the FreePascal compiler, including “railroad”
syntax diagrams. The descriptions are terse and should not be considered
tutorials in any sense of the word.
• The FreePascal Programmer’s Guide (prog.pdf) explains programming issues
that go beyond the fundamentals of Standard Pascal. This includes
language extensions, compiler directives, low-level issues like assembly
language interface and external calling conventions.
• The FreePascal Language Reference Guide (ref.pdf) explains how to install
and configure the compiler and how to invoke it. This includes compiler
modes, compiler error messages, debugging using gdb, and a list of the
standard units installed with the compiler. (The units are not described in
detail; for that see the Runtime Library Reference Guide.) A large part of the file
explains how to use FreePascal’s text-mode IDE fp.exe, which I won’t be
discussing in detail in this book.
• The Runtime Library (RTL) Units Reference Manual (rtl.pdf) describes all of
FreePascal’s standard units in detail, procedure by procedure. If you want
to know how to call the GotoXY procedure, this is where to look, keeping
in mind that the descriptions of individual procedures and functions are
necessarily brief and technical. Note: The file is 2,034 pages long.
• The Free Component Library Units Reference Manual (fcl.pdf) is a reference for
the FCL object-oriented library that is used for writing component-based
programs in Pascal. (Note that the FCL is not the same as the Lazarus
Component Library, or LCL.) The FCL is an advanced topic that I won’t be
covering in this book, so this file won’t be of much use to you in your first
steps as a Pascal programmer.
• The Compiler Switch Summary Chart (chart.pdf) is a two-page document with
a summary of global compiler switches on one page, and local compiler
switches on the other. Don’t worry if this isn’t meaningful right now;
most compiler switches aren’t used in tutorial examples.
There are HTML versions of all documentation volumes listed here except for the
compiler switch summary chart.
Installing FreePascal and Lazarus 95
All of the manuals mentioned above may be downloaded in either HTML or PDF
format from the FreePascal Web site:
https://www.freepascal.org/docs.var
The documentation for the version current at this writing (June 2019; v3.0.4) may
also be downloaded from SourceForge. The URL below will probably change when
new releases happen and the version number changes. Searching for “freepascal
documentation” will find the current documentation without difficulty.
https://sourceforge.net/projects/freepascal/files/Documentation/3.0.4
Paper Documentation
You can view the documentation in a Web browser, or download the doc files from
the Web, but having a paper copy of the manuals can be useful. All the documentation
files are available in PDF format, and if you’ve got a printer capable of duplexing, it
can be useful to print the files and place them in 3-ring or duo-tang binders. The
page size is A4, widely used in Europe, but the pages will print well if scaled to fit
American letter-size sheets.
Not all of it needs to be printed. The Runtime Library Reference Guide is an immense
file, but a lot of the material in the libraries is relatively arcane and won’t be anything
you’ll need to understand as a beginner. For your initial learning exercises, make sure
you have a PDF reader with a search function—and learn how to use it! I had a local
print shop print several documentation volumes for me with a spiral binding that
lies flat on the desktop or propped on a copy frame beside your monitor. Be aware
that some print shops may be hesitant to print copies of files that you obviously
didn’t create. In such cases it may help to point out that these are the manuals for free
software, and that there are no copyright notices in the files.
https://www.freepascal.org/download.var
If you’re reading this book a long time after I posted the file (currently, June 2019)
the latest stable version of FreePascal may not be the one I call out here. The current
version number will be shown on the page. Links to all the available platforms will
be there.
Clicking on a version will take you to a brief page allowing you to select a
download mirror. (I always use SourceForge.) Once the FreePascal Web site hands
you off to SourceForge, look first for a big green button reading “Download Latest
Version!” Click the button, and the installer file will begin coming down. If for some
reason the download doesn’t begin immediately, click on the direct link just under
the title reading, “Your FreePascal Compiler download will start shortly...”
This book shows examples running in a console window under Windows.
The installer for Windows is an ordinary Windows executable file. Close all open
Windows applications and run it, either from the Run window or by navigating to
the installer file and double-clicking on it. The installer will open a conventional
Windows install wizard. All of the wizard’s fields provide useful default values, so
you can accept all the default values in each one of the wizard’s dialogs. When the
wizard has completed its series of dialogs and terminated, you’ll see a new icon on
your desktop, for the FreePascal IDE.
Downloading Lazarus
If you install the Lazarus environment, FreePascal comes in right along with it. You
do not need to install FreePascal before you install Lazarus. As with FreePascal, there is a
Windows installer file for Lazarus. In short, you download the installer and run it,
accepting or tweaking the values presented by the install wizard as appropriate.
Note well that the Lazarus installer does not install FreePascal at C:\FPC, but instead
as a subdirectory beneath the Lazarus install directory.
The Lazarus Web site may be found here:
http://www.lazarus-ide.org/
This is the Lazarus “home page” and a good starting point when looking for the
latest on Lazarus. There’s a link to the Windows installer, and another link to the
Downloads section at the right margin, linked from the word “Other,” which will
take you to the Lazarus download page. (See Figure 4.1.) Click the link for the version
you want, which will take you to the file’s page on Sourceforge.
While you’re there, you might want to download the Lazarus documentation.
The Lazarus manuals do not exist as PDF files at this writing (June 2019) but rather
as .chm files. Given that .chm files are compiled HTML, you can open them with
(almost) any Web browser. The documentation folder may be found here:
https://sourceforge.net/projects/lazarus/files/Lazarus Documentation/
I generally read them online, starting at this link:
http://wiki.freepascal.org/Lazarus_Documentation
To run Lazarus, just double-click the desktop icon, as with any Windows app.
Note that when you install FreePascal as part of the Lazarus installation, the text-
mode IDE fp.exe will not be installed. If you want to experiment with fp.exe after
installing Lazarus, I suggest installing FreePascal separately. There’s nothing wrong
or hazardous about having two copies of FreePascal installed on a single hard drive,
though the second copy will cost you about 150 MB in disk space.
Installing FreePascal separately under Windows will create a desktop icon for
the text-mode IDE, and you run the IDE by double-clicking on the icon. Keep in
mind that the operation of the text-mode IDE can be erratic under Windows, so if
it crashes or looks peculiar, there may not be much you can do about it. It’s much
better to install and use Lazarus, which has a far superior IDE for Pascal.
Again, there is much more to Lazarus than you need in order to learn the
fundamentals of the Pascal language, and much more than I’ll be covering in this first
book. I recommend configuring Lazarus to hide the IDE windows that you won’t
need, to keep the clutter down and make the product easier to grasp. I’ll explain how
to do this in Chapter 5.
http://www.lazarus.freepascal.org/index.php?action=forum
100 FreePascal from Square One, Volume 1
http://software.opensuse.org/search
The OBS is a platform for distribution and installation of open source software.
It uses the YaST installation/configuration manager, and provides “1-click install”
buttons on the search page. Calling it “1-click” is a bit of an exaggeration, but the
big win is that you don’t have to type in the names of any packages or repositories,
enter any scripts, or do any manual unpacking or building. One click kicks off the
process, which launches an installation wizard. Run through the wizard, answer its
questions (which does require a few additional clicks) and YaST will begin installing
the selected package.
Here’s a step-by-step:
Installing FreePascal and Lazarus 101
for Lazarus has not been well-maintained over the last several years. The version of
Lazarus and FPC installed are not the latest, and (peculiarly) the FreePascal source
is not included in the package, even though Lazarus requires it. So if you install
Lazarus from one of the Debian packages downloadable at this writing (late 2017),
Lazarus will complain when you launch it that the FreePascal source code is not
available, and that some of its features (not stated) will not work completely.
This is not the fault of the people who wrote and maintain FreePascal and Lazarus,
because they do not have complete control over how the Debian package for the
product is maintained, which is a separate issue from maintaining the product itself.
So even if you find FreePascal and Lazarus in the Store, don’t install from it.
Instead, follow the instructions on this page in the FreePascal wiki:
http://wiki.freepascal.org/Lazarus_release_version_for_Ubuntu
The wiki page will provide a “canned” script that you can save and run to execute all
the server commands that the install requires. It also provides instructions step by
step, and you should read those simply to understand what the script is about and
how it works.
There are some specific instructions to be followed if you intend to run Lazarus
under Canonical’s Unity interface. I don’t like Unity and have not tried this, but I’m
guessing that it works just fine.
https://www.getlazarus.org/setup/
As with a lot of software under active and rapid development, things can change,
and the installation script available at getlazarus.org may not work on future
versions of the Raspberry Pi board or of FreePascal and Lazarus. You may have to
dig a little deeper on Google or ask questions on the Lazarus forums, as I describe
on the next page.
Installing FreePascal and Lazarus 103
http://www.lazarus.freepascal.org/index.php?action=forum
104 FreePascal from Square One, Volume 1
You have to register to post questions. Don’t hesitate to join and register; it’s
worthwhile. You will not get emails from them trying to sell you things, as so many
modern online forums do.
Do remember that others may have solved the same problems you’re having, possibly
a long time ago. Read over any posts regarding installation before posting what may be a
redundant question. In fact, plan to spend a day scanning the many threads to see what’s
there. You will learn a great deal, especially as a newcomer.
Chapter 5.
Configuring and Using
the Lazarus Environment
“G ive me a lever long enough, and a place to stand, and I will move the Earth.”
In perhaps his best-known statement, Archimedes was speaking literally
about the power of mechanical levers, but behind his words there is a larger truth
about work in general: To get something done, you need a place to work, with access
to tools. My radio bench down in my workshop is set up that way: A large, flat space
to lay ailing transmitters down, and a shelf above where my oscilloscope, VTVM,
frequency counter, signal generator, and dip meter are within easy reach.
Much of the initial success of Turbo Pascal forty-odd years ago was grounded in
that simple but compelling truth. For the first time, a compiler vendor had assembled
the most important tools of software development and put them together in an
intuitive fashion so that the various tasks involved in creating software flowed
easily and quickly from one step to the next.
105
106 FreePascal from Square One, Volume 1
At this writing (April 2025) Lazarus is still very actively in development. Now
that it has reached version 3.8 (with V4.0 in testing) it is definitely mature enough to
develop commercial applications with.
One thing to be aware of is that a lot of the way Lazarus is designed caters to
the needs of object-oriented programming (OOP) and you’ll need to learn the
fundamentals of Pascal before confronting OOP at all. The Object Inspector and
the Restriction Browser, in particular, make little sense until you “get” the object-
oriented idea. The Lazarus Component Library is completely object-oriented and is
not something that may be called from within very simple Pascal programs.
In fact, simple Pascal programs of the sort used in teaching can be created,
compiled, and debugged under Lazarus using just three windows: The main window
(which is a sort of expanded menu bar), the Source Editor window, and the Messages
window, where status reports and errors from the compiler are displayed.
108 FreePascal from Square One, Volume 1
A project-oriented IDE
Through most of the history of Pascal programming, a “project” in Pascal was
simply a Pascal program, or perhaps a program and a number of Pascal library files
named within the program and linked with the program to create the executable
program file. As Pascal programming has gotten more complex, and especially
with the advent of object orientation and GUI apps for Windows and the Linux
graphical shells, Pascal projects include more than simple Pascal code. You need
icon files, form files, and many other things in the general category called resources.
For this reason, when you want to write a program in FreePascal using Lazarus, you
don’t simply create a new Pascal source code file. You create a project.
A Lazarus project includes the main program’s source code file, obviously. It also
includes code libraries and other resources. Giving a project a single name (which
does not have to be the name of any source code file) gives you a sort of umbrella
beneath which you can tinker the various elements of an ambitious Pascal project
without losing track of what parts belong to what project.
After creating a project, you can add new files to the project whenever you need
to, and being able to treat the collection of files as a single named entity helps a lot in
managing the complexity of a sophisticated project. I recommend that you give each
project its own directory on disk to keep its files separate from those of other projects’.
appear in the Messages window. Note that not all messages appearing in the Messages
window indicate that something is wrong. Many simply tell you what FreePascal is
doing at that moment, and by displaying indicate that it succeeded.
This is done with the Save As... item under the File menu. If you intend to create a
directory for the project at the same time that you save it to disk, it’s a 2-step process:
1. Navigate to the parent directory (something like Lazarus Projects) and
click the Create New Folder icon. Enter a descriptive name for the new
folder that appears in the treeview and then click Enter.
2. Make sure that the name of the new folder is highlighted, and click Open.
Lazarus will then “move” to the new folder.
3. Enter a name for your project and click Save. Done!
If you have a mouse with a scroll wheel, the wheel will scroll the Source Editor
window up or down with one line per wheel click. The cursor does not follow the
scroll. This is useful to look up or down beyond the limits of the current screen
without moving your cursor away from the point in the source code where you’re
currently working.
If...Then...Else...
117
118 FreePascal from Square One, Volume 1
H aving gotten a feeling for both programming and the “big picture” of Pascal
itself, it’s now time to after the details of both the Pascal language and the
programming process. And therein lies a snag for me.
I’d like to be able to say, “Read this book from cover to cover and you’ll learn
Pascal.” That’s how I think books of this kind work best. The problem with teaching
Pascal in a strictly linear fashion is that it’s a continuous, linear narrative that weaves
all of Pascal’s ideas together in a single stream. There would be no “chapter” on
simple data types, because you have to introduce simple data types right alongside
reserved words, identifiers, operators, and so on. This reads well in the fashion of
a novel, but it doesn’t re-read very well. By that I mean that once you’ve read it, you
may feel that your understanding of derived types is fuzzy, and you may want to go
back and reread a discussion of derived types. But in a strictly linear exposition of
Pascal, derived types are discussed in no one place, but here, there, and in several
other places. Like all the other topics, they weave in now and again in the one big
story.
Once I’ve laid the groundwork it’ll be a lot easier. But in laying the groundwork
I’ve had to make a number of seemingly arbitrary decisions: Do I explain first what
reserved words and identifiers are, and then explain what data is? Or do I explain
data types first, so that I can explain how reserved words and identifiers come
together to make statements? Maybe it’s a silly thing to worry about. But I’m trying
hard to make this book accessible to total newcomers to the idea of programming,
and putting across the foundations of any idea is absolutely critical.
So you’ll have to cut me a little slack here. I’ve chosen to begin with Pascal’s
fundamental atoms like letters and symbols. In keeping with Pascal’s idea of
structure, I’ll go from there to reserved words and identifiers, and from there to
data types. After data types I’ll cover operators, expressions, and statements. That’s
mostly a linear, bottom-up approach, and it will help things come back to mind
when you return to these early chapters for review. Some might prefer that I cover
operators before the apparently more complex topic of data types, but trust me:
Data types without operators make more sense than operators without data types.
It will help a great deal if you’ve already read Chapter 3, which presents an
overview of most of the fundamental ideas of Pascal. If you haven’t, you ought to go
back and read Chapter 3 now. The overview material will definitely help bridge the
occasional chicken-and-egg dependencies you’ll find in the next several chapters.
And once you’ve got the core of the language under your belt, the problem goes
away.
Really!
Chapter 6.
Pascal Atoms
119
120 FreePascal from Square One, Volume 1
FOR I := 1 TO 17 DO
BEGIN
Shr(J);
Inc(J)
END;
However, after it removes excess whitespace, the compiler “sees” a continuous and
unformattted stream like this:
FOR I := 1 TO 17 DO BEGIN Shr(J); Inc(J) END;
The point here is that how you arrange the lines of code in your program is not
important to the FreePascal compiler. You can indent lines by two spaces per level (as
is my custom) or six or eight spaces. You can place two lines in between procedures
and functions, or three, or whatever you prefer. You can, if you like, place short
compound statements (that is, code falling between BEGIN and END reserved
words) on a single line. Pascal reserved words and identifiers must be separated
from one another by spaces. Beyond that, well, it’s up to you.
Some of the invisible 33 ASCII characters are “control characters” that were originally
designed to control the operation of mechanical teletype printers and later on, CRT
terminals. These are almost all obsolete nowadays and rarely used. Interestingly, one
such control character, the BEL character, was originally used to ring a small mechanical
bell in teletype machines, and later on caused CRT terminals to beep.
Pascal Atoms 121
Numeric Digits
Individually and together, the digits 0-9 are used by Pascal as you would expect: to
express literal numeric values:
8
71
29784
Pascal does not separate large values into groups of thousands with commas, as we
often do when writing ordinary text; for example, 29,784. Placing a comma within
a number will trigger a compiler error.
In addition to expressing literal numeric values, the digits 0-9 can also be part of
the identifiers you create as variable and procedure names; for example, Area51.
More on this a little later.
Alphabetic characters
The alphabetic characters A-Z and a-z are used individually and in combination
to create the names of compiler commands, reserved words, constants, variables,
procedures, and functions. Note well that unlike some other programming
languages (the C familiar, particularly) character case is not significant. For example,
the FreePascal compiler considers A and a to be the same character except when
present in quoted string literals. As I’ll explain later, within a string literal, case is
significant; ‘Jeff’ and ‘JEFF’ are considered different by the compiler.
Symbols
The ASCII character set includes a number of visible symbols that are neither
numeric digits nor letters of the English alphabet. These include punctuation marks
like ! and ?, arithmetic symbols like + and -, straight and curly brackets [ ] { }, slashes,
and a few others. Most such symbols have very specific meanings to the FreePascal
compiler. Some symbols can mean more than one thing; for example, the period
character, which expresses decimal parts of numeric literals, and also references to
fields within records. Sometimes two symbols are combined into a single symbol.
For example, the two symbols <> together mean “not equal to” and the symbols (*
begin a comment.
For the most part, symbols are not allowed within identifiers. That is, you cannot
create a variable named Ham&Cheese. The exception is the underscore character;
Ham_and_Cheese is perfectly legal. More on this later as well.
122 FreePascal from Square One, Volume 1
Table 6.2 lists the additional reserved words recognized by FreePascal when
operating in Delphi mode, using the $MODE DELPHI switch. Note well that these
are in addition to the reserved words in Table 6.1. When operating in Delphi mode,
FreePascal recognizes all of the reserved words in Table 6.1 as well as all the reserved
words in Table 6.2.
Pascal Atoms 123
When FreePascal is operating in its default FreePascal mode (or has been set to
FreePascal mode using the $MODE FPC switch) it enforces a few more reserved
words in addition to all of those enforced in Turbo Pascal 7 mode and Delphi mode.
These are listed in Table 6.3. Again, keep in mind that when operating in FreePascal
mode, the compiler enforces all reserved words listed in Tables 6.1 through 6.3.
In addition to the reserved words shown in Tables 6.1-6.3, there is another list of
predefined words that you should be aware of. These are called modifiers, and they
work in conjunction with certain reserved words to modify what those reserved
words mean to the compiler, hence their name. For the most part, you will have no
call to use them while you’re just learning FreePascal. However, it is important that
you not use them for anything else in your own programs.
The compiler has its own somewhat arcane uses for them, but it will also allow
you to use them if you choose to, as the names of variables, constants, procedures,
or functions. That, however, is a bad idea. I recommend treating the list of modifiers
in Table 6.4 as though they were additional reserved words, especially when you’re
just starting out. To avoid confusion, especially as you advance in your skills, use
124 FreePascal from Square One, Volume 1
them only where and how they were designed to be used by FreePascal’s authors. All
of them are considered advanced to very advanced topics, and I will be discussing
few if any of them in this first book. For now, treat them as a list of words to be
avoided as you define named elements in your own programs.
6.3. Identifiers
Your computer creates programs for your use. Programs are collections of things
(data) and steps to be taken in storing, changing, and displaying those things
(statements). The computer knows such things by their addresses in its memory.
The readable, English-language names of data, of programs, of functions and
procedures, are for your benefit. We call such names, taken as a group, identifiers.
They exist in the source code only. Identifiers do not exist in the final executable code file.
Any name that you invent and apply to an entity in a program is an identifier.
The names of variables, of data types, of named constants, of procedures and
functions, and of the program itself are identifiers. An identifier you create can
mean whatever you want it to mean (within Pascal’s own rules and limits) as
long as it is unique within its scope. There are also identifiers that the compiler
predefines, and there are identifiers defined in code libraries that FreePascal allows
you to use.
Pascal Atoms 125
The notion of scope is a subtle and important concept that I will explain at length
later on, but broadly put, it indicates what portion of your program the compiler
can “see” at any given point in your code. The compiler will complain if it can “see”
more than one item with the same identifier. That is, if you have a variable named
Counter you cannot have a procedure named Counter within the same scope. Nor
can you have another variable (or anything else) named Counter within that scope.
Understanding scope requires that you understand more about program structure
than I’ve explained so far; hold on until we get there.
FreePascal identifiers are sequences of characters of any length up to 255
characters that obey these few rules:
• Legal characters include letters, digits, and underscores. Spaces and
symbols like &,!,*, or % (or any symbol not a letter or digit) are not
allowed.
• Digits (0-9) may not be used as the first character in an identifier. All
identifiers must begin with a letter from A-Z (or lowercase a-z) or an
underscore.
• Identifiers may not be identical to any reserved word.
• Case differences are ignored. The letter A is the same as a to the compiler.
• Underscores are legal and significant. Note that this differs from
most older, non-Borland Pascal compilers, in which underscores are
legal but ignored. You may use underscores to “spread out” a long
identifier and make it more readable: Sort_On_ZIP_Code rather than
SORTONZIPCODE. (A better method that has become the custom is to
use “camel case” to accomplish the same thing: SortOnZIPCode.)
• All characters are significant in a FreePascal identifier, up to 255
characters. Some ancient Pascal compilers allowed identifiers of arbitrary
length but ignored any character after the eighth character.
The following are all invalid identifiers that will generate errors:
generates. And that amount of space is…none! Identifiers do not exist in your executable
binary program. They are used by FreePascal to build a “symbol table” of memory
addresses that allow it to generate the executable code file, but identifiers are left
behind when the work is done.
With that in mind, create identifiers that help you read your source code. That’s what
they’re for. An identifier can be too long, of course, but that’s rarely the problem.
A value on the right side of the assignment operator is assigned to the variable on
the left side of the assignment operator. In an assignment statement, there is always
a variable on the left side of the assignment operator. On the right side may be a
constant, a variable, or an expression.
Expressions
An expression in Pascal is a combination of data items and operators that eventually
“cook down” to a single value. Data items are constants and variables. The best way
to understand expressions is to think back to your grade-school arithmetic, and
how you used arithmetic operations to combine two or more values into a single
result. This is an expression, both in basic arithmetic and in Pascal:
17 + 3
The addition operator + performs an add operation on its two operands, 17 and 3.
The value of the expression is 20.
An expression like “17 + 3”, while valid in Pascal, would not be used in a real
program, where the literal value “20” would suffice. It’s a lot more useful to create
expressions that involve variables. For example:
Pi * Radius * Radius { Pi is a predefined constant in FreePascal }
This expression’s value is recognizable as the area of the circle defined by whatever
value is contained in the variable Radius. Note here that a Pascal expression is not
the same thing as a Pascal statement, and expressions do not stand alone. To pass
the compiler’s picky standards, an expression must always be part of a statement.
Standard Pascal includes a good many different operators for building expressions,
and FreePascal enhances Standard Pascal with a few additional operators. They
fall into a number of related groups depending on what sort of result they return:
Relational, arithmetic, set, string, and logical (also called “bitwise”) operators.
128 FreePascal from Square One, Volume 1
The column labeled “Precedence” has to do with order of evaluation, which I’ll
return to later on in this book.
The set operators fall into two separate worlds; they’re set operators, obviously,
but they also express certain relationships between sets and set members that return
Boolean values. (I’ll come back to sets later on.) The convention is that any operator
that returns a Boolean value is relational, since Boolean values express the “truth or
falsehood” of some stated relation. The three relational operators that involve sets,
set membership and the two set inclusion operators, will be discussed along with the
Pascal Atoms 129
operators that return set values, in Section 8.3. Note that the set inclusion operators
share symbols with the greater than or equal to/less than or equal to operators, but
the sense of these two types of operations is radically different.
Some of the types mentioned briefly in this section are types I haven’t yet
explained in detail. Most of these will be covered in the next chapter except for
pointers, which will have to wait until considerably later on. The mentions are here
not so much for you to read on your first linear pass through this book, but on those
occasions when you return to this section for a brushup.
Equality
If two values compared for equality are the same, the expression will evaluate as
True. In general, for two values to be considered equal by FreePascal’s runtime code,
they must be identical on a bit-by-bit basis. This is true for comparisons between like
types. Most comparisons must be done between values of the same type.
The exceptions are comparisons done between numeric values expressed as
different types. FreePascal allows comparisons rather freely among integer types
and real number types, but this sort of type-crossing must be done with great care.
In particular, do not compare calculated reals (real number results of a real number
arithmetic operation) for equality, either to other reals or to numeric values of other
types. Rounding effects may cause real numbers to appear unequal to compiled
code even though the mathematical sense of the calculation would seem to make
them equal.
Integer types Byte, ShortInt, Word, Integer, and LongInt may be freely
compared among themselves.
Two sets are considered equal if they both contain exactly the same members.
(The two sets must, of course, be of the same base type to be compared at all.) Two
pointers are considered equal if they both point to the same dynamic variable. Two
pointers are also considered equal if they both contain the predefined value NIL.
Two records are considered equal if they are of the same type (you cannot
compare records of different types) and each field in one record is bit-by-bit identical
to its corresponding field in the other record. Remember that you cannot compare
records, even of the same type, using the greater than/less than operators >, <, >=,
or <=.
Two strings are considered equal if they both have the same logical length (see
Section 8.6) and contain the same characters. This makes them bit-by-bit identical
out as far as the logical length, which is the touchstone for all like-type equality
comparisons under Turbo Pascal. Remember that this makes leading and trailing
blanks significant:
130 FreePascal from Square One, Volume 1
Inequality
The rules for testing for inequality are exactly the same as the rules for equality. The
only difference is that the Boolean state of the result is reversed:
17 = 17 { True }
17 <> 17 { False }
42 = 17 { False }
42 <> 16 { True }
In general, you can use the inequality operator anywhere you can use the equality
operator.
Pointers are considered unequal when they point to different dynamic variables,
or when one contains the value NIL and the other does not. The bit-by-bit rule is
again applied: Even one bit’s difference found during a like-type comparison means
the two compared operands are unequal. The warning applied to rounding errors
produced in calculated reals applies to inequality comparisons as well.
The Char and Byte types are both limited to 256 possible values, and both have
an order implied by the sequence of binary numbers from 0 to 255. The Char type is
ordered by the ASCII character set, which makes the following expressions evaluate
to True:
The higher 128 values assignable to Char variables have no truly standard character
glyphs outside of the PC world, but they still exist in fixed order and are numbered
from 128 to 255.
Enumerated types are limited to no more than 255 different values, and usually
have fewer than ten or twelve. Their fixed order is the order the values were given in
the definition of the enumerated type:
TYPE
Colors = (Red,Orange,Yellow,Green,Blue,Indigo,Violet);
The ordering of string values involves two components: The length of the string
and the ASCII values of the characters present in the string. Essentially, FreePascal
begins by comparing the first characters in the two strings being compared. If those
two characters are different, the test stops there, and the ordering of the two strings
is based upon the relation of those two first characters. If the two characters are the
same, the second characters in each string are compared. If they turn out to be the
same, then the third characters in both strings are compared.
This process continues until the code finds two characters that differ, or until
one string runs out of characters. In that case, the longer of the two is considered to
be greater than the shorter. All of the following expressions evaluate to True:
The parentheses indicate that the expression within the parentheses is evaluated
first, and only then is the resultant value acted upon by NOT. This expression is an
instance where “order of evaluation” becomes important. I’ll discuss this in detail in
Section 6.7.
AND (also known as “conjunction”) requires two operands, and follows this
rule: If both operands are True, the expression returns True; else the expression returns False.
If either operand or both operands have the value False, the value of the expression
as a whole will be False. Some examples:
All of these example expressions use constants, and thus are not realistic uses of
AND within a program. We present them this way so the logic of the statement is
obvious without having to remember what value is currently in a variable present
in an expression. We’ll be presenting some real-life coding examples of the use of
NOT, AND, and OR in connection with the discussion of order of evaluation in
Section 6.7.
Pascal Atoms 133
OR (also known as “disjunction”) requires two operands, and follows this rule:
If either (or both) operands is True, the expression returns True; only if both operands are False
will the expression return False.
Some examples, again using constants:
Finally, there is XOR, which also requires two operands, and follows this rule: If
both operands are the same Boolean value, XOR returns False; only if the operands have unlike
Boolean values will XOR return True. Some examples:
X >= Y
(X > Y) OR (X = Y)
X <= Y
(X < Y) OR (X = Y)
134 FreePascal from Square One, Volume 1
The rules for applying >= and <= are exactly the same as those for < and >. They
may take only scalars or strings as operands.
Note that for the purposes of the table, “Integer” types include Integer, LongInt,
ShortInt, and SmallInt. “Cardinal” types include Word, Byte, LongWord,
Cardinal. “Real” types include Real, Single, Double, Extended, and Currency.
The old Comp type inherited from Borland Pascal is also considered a real type, but
it is not implemented on all platforms and I recommend that you not use it. It was
necessary at the time (back in the 1980s!) but there are better and more standard
real number types to use today.
There are a lot of numeric types, and there are “gotchas” connected with some of
them. I’ll explain numeric types in a great deal more detail in the next chapter.
The Table 6.6 “operands” column lists those data types that a given operator may
take as operands. The compiler is fairly free about allowing you to mix types of
numeric variables within an expression. In other words, you may multiply bytes by
integers, add reals to bytes, multiply integers by bytes, and so on. For example, these
are all legal expressions in FreePascal:
Pascal Atoms 135
VAR
I,J,K : Integer;
R,S,T : Real;
A,B,C : Byte;
U,V : Single;
W,X : Double;
Q : ShortInt;
L : LongInt;
The “result type” column in the table indicates the data type that the value of an
expression incorporating that operator may take on. Pascal is ordinarily very picky
about the assignment of different types to one another in assignment statements.
This “strict type checking” is relaxed to some extent in simple arithmetic expressions.
Numeric types may, in fact, be mixed fairly freely within expressions as long as a
few rules are obeyed:
1. Any expression including a floating point value may only be assigned
to a floating-point variable.
2. Expressions containing floating point division (/) may only be assigned
to a floating point variable even if the operands are integer types.
Failure to follow these rules will generate a type mismatch error. However, outside of the
two restrictions above, a numeric expression may be assigned to any numeric variable,
assuming the variable has sufficient range to contain the value of the expression. (More
on range in the next chapter.) For example, if an expression evaluates to 14,000, you
should not assign the expression to a variable of type Byte, which can only contain
values from 0 to 255. Program behavior in cases like that is unpredictable. If range
checking is on, such an assignment will generate a range error.
Addition, subtraction, and multiplication are handled the same way ordinary
arithmetic is handled with pencil or calculator. Division is a little trickier.
Division
There are three distinct division operators in Pascal. One supports floating point
division, and the other two support division for integer and cardinal types. Floating
point division (/) may take operands of any numeric type, but it always produces a
136 FreePascal from Square One, Volume 1
floating point value, complete with decimal part. Attempting to assign a floating
point division expression to an integer type will generate error a type mismatch
error, even if all numeric variables involved are integers.
VAR
I,J,K : Integer;
I := J / K; { Won’t compile!!! }
Why? Dividing two integers can generate a decimal part, and type Integer cannot
express a decimal part.
Division for numbers that cannot hold decimal parts is handled much the same
way division is first taught to grade schoolers: When one number is divided by
another, two numbers result. One is a whole number quotient; the other a whole
number remainder.
In Pascal, integer division is actually two separate operations that do not depend
upon one another. One operator, DIV, produces the quotient of its operands:
J := 17;
K := 3;
I := J DIV K; { I is assigned the value 5 }
No remainder is generated at all by DIV, and the operation should not be considered
incomplete. If you wish to compute the remainder, the modulus operator MOD is
used:
I := J MOD K; { I is assigned the value 2 }
Assuming the same values given above for J and K, the remainder of dividing J by K
is computed as 2. The quotient is not calculated at all (or calculated internally and
thrown away); only the remainder is returned.
Sign inversion
Sign inversion is a “unary” operator; that is, it takes a single operand. What it does is
reverse the sign of its operand. It will make a positive quantity negative, or a negative
quantity positive. It does not affect the absolute value (distance from zero) of the
operand. Sign inversion can only be used with signed numeric types, which include
the real number types plus ShortInt, SmallInt, Integer, LongInt, and Int64.
Note that sign inversion cannot be used with cardinal types like Cardinal,
Byte, Word, or LongWord. Cardinal types are “unsigned”; that is, they are never
considered negative, and so changing the sign of the value is impossible. I’ll have a
Pascal Atoms 137
The best way to approach all bitwise operators is to work in true binary notation,
where all numbers are expressed in base two, and the only digits are 1 and 0. The
bitwise operators work on one binary digit at a time. The result of the various
operations on 0 and 1 values is best summarized by four “truth tables”:
1 AND 0 = 0 1 OR 0 = 1 1 XOR 0 = 1
1 AND 1 = 1 1 OR 1 = 1 1 XOR 1 = 0
When you apply bitwise operators to two 8-bit or two 16-bit data items, it is the
same as applying the operator between each corresponding bit of the two items.
For example, the following expression evaluates to True:
$80 = ($83 AND $90) { All in hexadecimal }
Hex Binary
$83 = 1 0 0 0 0 0 1 1
AND
$90 = 1 0 0 1 0 0 0 0
=
$80 = 1 0 0 0 0 0 0 0
Read down from the top of each column in the binary number, and compare the
little equation to the truth table for bitwise AND. If you apply bitwise AND to each
column, you will find the bit pattern for the number $80 to be the total result.
Now, what good is this? Suppose you only wanted to examine four out of the
eight bits in a variable of type Byte. The bits are numbered 0-7 from the right. The
bits you need are bits 2 through 5. The way to do it is to use bitwise AND and what
we call a “mask:”
VAR
GoodBits, AllBits : Byte;
To see how this works, let’s again “spread it out” into a set of binary numbers:
AllBits = X X X X X X X X
AND
$3E (mask) = 0 0 1 1 1 1 0 0
=
GoodBits = 0 0 X X X X 0 0
Here, “X” means “either 1 or 0.” Again, follow the eight little operations down from
the top of each column to the bottom. The zero bits present in four of the eight
columns of the mask, $3C, force those columns to evaluate to zero in GoodBits,
regardless of the state of the corresponding bits in AllBits. Go back to the truth
table if this is not clear: If either of the two bits in a bitwise AND expression is zero, the result
will be zero.
Pascal Atoms 139
This way, we can assume that bits 0,1,7 and 8 in GoodBits will always be zero
and we can ignore them while we test the others.
Shift operators
We’ve looked at bit patterns as stored in integer types, and how we can alter those
patterns by logically combining bit patterns with bitmasks. Another way to alter
bit patterns in integer types is with the shift operators, SHR and SHL. SHR stands
for SHift Right; SHL for SHift Left.
Both operators are best understood by looking at a bit pattern before and after
the operator acts upon it. Start with the value $CB (203 decimal) and shift it two
bits to the right as the SHR operator would do:
1 1 0 0 1 0 1 1 -->
\ \ \ \ \ \
\ \ \ \ \ \
--> 0 0 1 1 0 0 1 0
The result byte is $32 (50 decimal). The two 1-bits on the right end of the original
$CB value are shifted off the end of the byte (into the “bit bucket,” some say) and are
lost. To take their place, two 0-bits are fed into the byte on the left end.
SHL works identically, but in the other direction. Let’s shift $CB to the left with
SHL and see what we get:
<-- 1 1 0 0 1 0 1 1
/ / / / / /
/ / / / / /
0 0 1 0 1 1 0 0 <--
Again, we lose two bits off the end of the original value, but this time they drop off
the left end, and two 0-bits are added in on the right end. What was $CB is now $2C
(44 decimal.)
Syntactically, SHL and SHR are like the arithmetic operators. They act with the
number of bits to be shifted to form an expression, the resulting value of which you
assign to another variable:
Result := Operand <SHL/SHR> <number of bits to shift>;
Some examples:
VAR
B,C : Byte;
I,J : Integer;
140 FreePascal from Square One, Volume 1
I := 17;
J := I SHL 3; { J now contains 136 } B := $FF;
C := B SHR 4; { C now contains $0F }
It would be a good exercise to work out these two examples shifts shown above on
paper, expressing each value as a binary pattern of bits and then shifting them.
An interesting note on the shift operators is that they are extremely fast ways to
multiply and divide a number by a power of two. Shifting a number one bit to the
left multiplies it by two. Shifting it two bits to the left multiplies it by four, and so
on. In the example above, we shifted 17 by three bits, which multiplies it by 8. Sure
enough, 17 X 8 = 136.
It works the other way as well. Shifting a number one bit to the right divides
it by two; shifting two bits to the right divides by four, and so on. The only thing
to watch is that there is no remainder on divide and nothing to notify you if you
overflow on a multiply. It is a somewhat limited form of arithmetic, but in time-
critical applications you’ll find it is much faster than the more generalized multiply
and divide operators.
How will the compiler interpret this? Which operator is applied first? As you
might expect, there are rules that dictate how expressions containing more than
one operator are to be evaluated. These rules define what we call order of evaluation.
To determine the order of evaluation of an expression, the compiler must consider
three factors: Precedence of operators, left to right evaluation, and parentheses.
Precedence
All operators in Pascal have a property called precedence. Precedence is a sort of
evaluation prioritizing system. If two operators have different precedences, the one
with higher precedence is evaluated first. There are five degrees of precedence: 1 is
the highest and 5 the lowest.
When we summarized the various operators in tables, the rightmost column contained
each operator’s precedence. The sign inversion operator has a precedence of one. No other
Pascal Atoms 141
operator has a precedence of one. Sign inversion operations are always performed before
any other operations, assuming parentheses are not present. (We’ll get to that shortly.)
Logical and bitwise NOT operators have a precedence of two. For example:
VAR
OK,FileOpen : Boolean;
VAR
I,J,K : Integer;
J := I * 17 DIV K;
The * and DIV operators both have a precedence of 3. To evaluate the expression
I * 17 DIV K, the compiler must first evaluate I * 17 to an integer value, and then
integer divide that value by K. The multiplication operator * is to the left of DIV,
and so it is evaluated before DIV.
Note that left to right evaluation happens only when it is not clear from precedence
(or parentheses, see below) which of two operators must be evaluated first.
The idea here is to test the Boolean values of two relational expressions. The
precedence of AND is greater than the precedence of any relational operator like
> and <=. So the compiler would attempt to evaluate the subexpression J AND K
first.
Actually, this particular expression does not even compile; FreePascal will flag
an error as soon as it finishes compiling J AND K and sees another operator ahead
of it.
The only way out of this one is to use parentheses. Just as in the rules of algebra,
in the rules of Pascal, parentheses override all other order of evaluation rules. To
make the offending expression pass muster, you must rework it this way:
(I > J) AND (K <= L)
Now the compiler first evaluates (I > J) to a Boolean value, then (K <= L) to another
Boolean value, then submits those two Boolean values as operands to AND. AND
happily generates a final Boolean value for the entire expression.
This is one case (and a fairly common one) in which parentheses are required in
order to compile the expression without errors. However, there are many occasions
when parentheses will make an expression more readable, even though, strictly
speaking, the parentheses are not required:
(Pi * Radius) + 7
Here, not only does * have a higher precedence than +, * is to the left of + as well.
So in any case, the compiler would evaluate Pi * Radius before adding 7 to the
result. The parentheses make it immediately obvious what operation is to be done
first, without having to think back to precedence tables and consider left to right
evaluation.
I’m a bit of a fanatic about program readability. Which of these (identical)
expressions is easier to dope out:
R + 2 * Pi - 6
R + (2 * Pi) - 6
You have to think a little about the first. You don’t have to think about the second
at all. I powerfully recommend using parentheses in all but the most completely
simpleminded expressions to indicate to all persons (including those not especially
familiar with Pascal) the order of evaluation of the operations making up the
expression. Parentheses cost you nothing in code size or code speed. Nothing at all.
To add to your program readability, that’s dirt cheap.
Pascal Atoms 143
6.8. Statements
Put as simply as possible, a Pascal program is a series of statements. Each statement
is an instruction to do something: to define data, to alter data, to execute a function
or procedure, to change the direction of the program’s flow of control.
Perhaps the simplest of all statements is an invocation of a procedure or function.
FreePascal includes a library containing several screen-control procedures for use
in text program in console windows. These procedures enable your programs to
move the text cursor around, clear the screen, and so on. Procedures in Pascal are
generally used by naming them, and naming one constitutes a statement:
ClrScr;
This statement tells the computer to do something; in this case, to clear the screen.
The computer executes the statement; the screen is cleared, and control passes to
the next statement, whatever that may be.
We have been using assignment statements, type definition statements, and variable
declaration statements all along. By now you should understand them reasonably well.
Type definition statements must exist in the type definition part of a program, procedure,
or function. They associate a type name with a description of a programmer-defined
type. Enumerated types (see Section 8.2) are an excellent example:
TYPE
Spectrum = (Red,Orange,Yellow,Green,Blue,Indigo,Violet);
LongColors = Red..Yellow;
ColorSet = SET OF Spectrum;
Each of these three definitions is a statement. The semicolons separate the statements
rather than terminate them. This is a critical distinction that frequently escapes
beginning Pascal programmers. I’ll take the vexing matter of semicolons up again
in Section 9.8.
Variable declaration statements are found only in the variable declaration part
of a program, function, or procedure. They associate a variable name with the data
type the variable is to have. The colon symbol (:) is used rather than the equal sign:
VAR
I,J,K : Integer;
Ch : Char;
R : Real;
operator to the variable on the left. Only a variable can be to the left of the assignment
operator. Constants, literals, and expressions must be on the right side. Other
variables, of course, can also be to the right of the assignment operator:
This is only a quick description of the simplest statement types. We’ll return
in detail to the topic of statements in Chapter 9, when we confront Pascal’s “flow
control” machinery: Loops and conditional statements.
The variable’s identifier comes first, followed by a colon and then the name of the
type that you choose to define that variable by. The definition shown above allows
you to manipulate integer values in a variable called CreditHours, subject to Pascal’s
explicit limitations on what can be done with Integer data types.
I didn’t show it explicitly in the above definition, but variable definitions must
reside in the variable definition section of a Pascal program. The reserved word VAR
begins the variable definition section, and it runs until another program section
(say, a procedure or a function, or the main program) begins. The variable definition
section of a program (and there may be more than one in FreePascal) is where you
give names to variables and assign them types. We’ll speak more of the several
sections of a Pascal program when we dig deep into program structure later on.
In the meantime, you can refer back to a complete Pascal program with a variable
definition section on Page 72.
145
146 FreePascal from Square One, Volume 1
take on a negative value. That is, it can have neither a positive nor a
negative sign in front of it, as can most of the standard numeric types.
Byte values are always assumed to be positive. Byte gets its name from
the fact that it occupies one single byte in memory. (The Word type
occupies two bytes, and some of FreePascal’s more esoteric numeric types
occupy as many as ten.)
• ShortInt is a numeric type that resembles Byte in that it occupies one
byte of memory. Unlike Byte, it can represent signed values, in the range
-128..127. Type ShortInt is, in fact, a “signed byte.”
• SmallInt is “small integer” and serves as a signed numeric type that can
represent values in the range -32768..32767. It occupies two bytes in
memory.
• Word is like Byte in that it is unsigned and cannot hold negative values.
Like SmallInt it occupies two bytes of memory. Word may express values
in the range 0..65535.
• LongInt is what its name implies: A “long integer.” It occupies four bytes
in memory rather than two, as the Word and SmallInt types do. Its range
is hugely greater, however, and can express values from -2147483648 to
2147483647. Sidenote: You cannot use commas to break up the expression
of large numbers in Pascal, as we often do in ordinary day-to-day
correspondence. So while spitting out a number like 2147483647 seems
awkward without the commas, do get used to it: it’s simply the way we do
things in programming.
• LongWord is related to LongInt, in that it occupies four bytes in
memory. It is unsigned, and may express positive values in the range
0..4294967295.
• Int64 is a signed type and can hold both negative and positive values. It
occupies eight bytes in memory, or 64 bits, hence its name. Take a deep
breath: Its range is -9223372036854775808 on the low side ( negative nine
quintillion!) to 9223372036854775807 on the high side.
• QWord is an unsigned type that occupies eight bytes in memory. It’s the
unsigned partner of Int64. Its range is 0..18446744073709551615. At
some point naming huge numbers becomes numbing and meaningless,
but just for fun, that’s zero to eighteen quintillion.
This list does not include the real number types in FreePascal. In programming,
real numbers are those containing a decimal part, like 3.14159. There are several real
number types in FreePascal , and I’ll address them in detail later, in Section 7.7.
Data and Data Types 149
In this line of code, “4” and “3” are literal constants, representing the numeric values
of 4 and 3.
There is another constant in that statement: The identifier Pi, which is predefined
in FreePascal as the real number value 3.14159265358979. You can define your
own constants as you need them. The constant declaration part begins with the
reserved word CONST and runs until some other part of the program begins. (You
do not need to end the CONST section with END.) The constant declaration part is
typically very early in a program, and is often the very first part of a program after
the program name itself:
Here, Pi denotes a named constant. We could as well have used the literal constant
3.14159 in the statement, but “Pi” is shorter and makes the expression less cluttered
and more readable. And more precise: FreePascal’s Pi constant goes out 14 places!
Especially where numbers are concerned, named constants almost always make a
program more readable.
Another use for constants is in the setting of program parameters that need
changing only very rarely. They still might be changed someday, and if you use a
named constant, changing the constant anywhere in the program is only a matter of
changing the constant’s declaration once in the constant declaration part of the
program and then recompiling.
The alternative is to hunt through hundreds or thousands of lines of source code
to find every instance of a literal constant to change it. You will almost certainly
miss at least one, and the resultant bug explosion may cost you dearly in time and
torn hair.
Data and Data Types 151
In short, don’t use literal constants anywhere you will ever anticipate needing
changes. In mathematical formulae literal constants are usually OK; but keep in
mind that using a named constant in place of a literal constant allows you to control
the precision of the constant everywhere in the program, by the use of a single
constant definition. You may want to use pi to eight decimal places initially, but
later on, to improve program performance, you may decide that five decimal places
is plenty. If you define the mathematical constant pi in a named constant at the
front of your program, you can change the precision instantly just by changing the
definition—and not have to worry about forgetting one or two places where you
had hard-coded a specific value of pi into an expression.
I haven’t said much about strings yet (and will cover them in detail in Chapter
8) but string literals are easy to understand: You enclose some sequence of ASCII
characters between single-quote marks:
‘Call me Ishmael. In fact, call me anything but late for dinner.’
Literal string constants like this may be assigned to string variables later on in
the program.
152 FreePascal from Square One, Volume 1
because Answer has been defined as a constant that already has a value of 42.
There is an important difference between constants and variables. In FreePascal,
simple constants are written into the code by the compiler as what we call immediate
data. This is difficult to explain if you don’t have some grasp of how computers work
down at the silicon level, but think of it this way: The value represented by a simple
constant is written into your program every place you use it. If you were to define the
Answer constant as shown above and reference it twelve times in your program,
the compiler would store Answer’s equivalent binary value (here, 42) twelve times
in your program code.
Variables, by contrast, are kept separate from the code portion of a program, and
each variable is stored in only one place. Your program has a data segment in memory
where it stores its variables, and a named variable is present in the data segment
only once. To access the value stored in a variable, your program must use a memory
reference into the data segment to the place where the variable’s value is stored.
This isn’t something you need to know a lot about, and (especially while you’re a
beginner) where a variable is stored in memory and how the variable is represented
in memory aren’t terribly important. The important thing to remember is this:
• A constant is defined at compile time and cannot be changed while your
program is running.
• A variable is not given a value until your program gives it one at runtime,
and that value may be changed by your program at any point while the
program runs.
The type of a constant depends, to some extent, on its context. Consider:
PROGRAM AllTheAnswers;
CONST
Answer = 42;
VAR
Tiny : Byte; { One byte }
Little : ShortInt { A short integer (1 byte) }
Small : Integer; { An integer (2 bytes) }
Data and Data Types 153
BEGIN
Tiny := Answer;
Little := Answer;
Small := Answer;
Big := Answer;
Huge := Answer;
END.
In the code snippet given above, Answer’s value is defined as 42. But it is perfectly
legal to assign the value of Answer to type Byte, type Integer, type ShortInt, type
LongInt, or type Real. The code the compiler generates to do the assignment in each
case is a little different, and that code is smart enough to translate the numeric value
“42” into a binary number that will be properly expressed as type Byte, Integer,
ShortInt, LongInt, or Real, or any other numeric type supported by the compiler.
But the end result is that all five variables of five different types will each express a
numeric value of 42 in its own fashion.
I cover the hexadecimal system and hex numbers in great detail in my print book,
Assembly Language Step By Step, Third Edition (John Wiley & Sons, 2009).
Inside string literals, lower case and upper case characters are distinct. If you
wish to include a single quote mark as a character inside a string literal, you must
use two single quotes together:
Writeln(‘>>You haven’’t gotten it right yet...’);
CONST
Platter = 1;
FirstSide = Odd(Platter); { Boolean }
FlipSide = NOT FirstSide; { Boolean}
LongSide = 17;
ShortSide = 6;
TankDepth = 8;
Volume = LongSide * ShortSide * TankDepth; { Constant expression }
CONST
ComPort = 1; { 1 = COM1: 2 = COM2: }
ComBase = $2F8; { Build on this “magic number” }
{ Base I/O port is $3F8 for COM1: and $2F8 for COM2: }
PortBase = ComBase OR (ComPort SHL 8);
Don’t panic if most of this doesn’t make immediate sense to you! What matters
here is knowing how each of the identifiers in the example above is given a value.
Every one of the constants defined in the example code fragment has a different
value depending on whether the COM1: or COM2: serial ports is to be used. By
changing the value of the constant ComPort from 1 to 2, all the other constants
change accordingly to the values that apply to serial port COM2:. The program does
not need to be peppered with magic numbers like $2FC and $3FA. Also, your program
does not need to spend time initializing all these port numbers as variables, because
the compiler does all the calculation at compile time, and the resulting values are
inserted as immediate data into the code generated from your source file.
The other use for constant expressions helps your programs document themselves.
You may need some sort of mathematical “fudge factor” in a complicated program.
You can define it as a simple named real-number constant:
FudgeFactor = 8.8059;
No one, looking at the literal numeric value, would have any idea of its derivation.
If the value is in fact the result of an established formula, it can help readability to
make the formula part of a constant expression:
ZincOxideDensity = 5.606;
FudgeFactor = ZincOxideDensity * (Pi / 2);
This will help others (or maybe even you) keep in mind that you had to fudge things
by multiplying the density of zinc oxide by pi over 2. (Note: I’ve deliberately made this
“fudge factor” ridiculous. Many are.) The idea should never be far from your mind that
158 FreePascal from Square One, Volume 1
Pascal programs are meant to be read. If you can’t read them, you can’t change them
(or fix them) and then you might as well throw them away and start from scratch. Do
whatever you can to make your programs readable. You (or whoever will someday
inherit and have to work with your code) will be glad you did.
Characters
The best way to explain Pascal’s ordinal types is through a close look at the most
common such type: Char. Type Char (character) is a Standard Pascal type, present
in all implementations of Pascal. Type Char includes the familiar ASCII character set:
Letters, numbers, common symbols, and the control characters like carriage return,
backspace, tab, etc. There are 128 characters in the ASCII character set. But type Char
actually includes 256 different values, since a character is expressed as an eight-bit
byte. (Eight bits may encode 256 different values.) The “other” 128 characters have
no standard names or meanings in the ASCII character set. When displayed on a
device that supports their glyphs, the “high” 128 characters show up as non-English
characters, fractions, segments of boxes, or mathematical and other symbols.
How, then, to represent such characters in your program? The key lies in the
concept of ordinality. There are 256 different characters included in type Char. These
characters exist in a specific ordered sequence numbered 0,1,2,3 and onward up to
255. The 65th character (counting from 0, remember) is always capital A. The 32nd
character is always a space, and so on.
An ordinal number is a number indicating a position in an ordered series. A
character’s position in the sequence of type Char is its ordinality. The ordinality of
capital A is 65. The ordinality of capital B is 66, and so on. Any character in type
Char can be expressed by its ordinality, using the standard “transfer function”
Chr. A capital A may be expressed as the character literal ‘A’, or as Chr(65). The
expression Chr(65) may be used anywhere you would use the character literal ‘A’.
Data and Data Types 159
Beyond the limits of the displayable ASCII character set, the Chr function is the
only reasonable way to express a character. The character expressed as Chr(234) will
display on the PC-compatible screen as the Greek capital letter omega (Ω) but may
be displayed as some other glyph on another computer that is not PC-compatible. It
is best to express such characters using the function Chr.
What will Pascal allow you to do with variables of type Char?
1. You can write them to the console display or printer using Write and
Writeln:
Writeln(‘A’);
Write(Chr(234));
Write(UnitChar); { UnitChar is a variable of type Char }
2. You can concatenate them with string variables using the string
concatenation operator (+) or the Concat built-in function. (See Section
12.2):
ErrorString := Concat(‘Disk error on drive ‘,UnitChar);
DriveSpec := UnitChar + ‘:’ + FileName;
3. You can derive the ordinality of characters with the Ord transfer function:
BounceValue := 31+Ord(UnitChar);
7.5. Booleans
Type Boolean is part of ISO Standard Pascal. A Boolean variable has only two
possible values, True and False. Like type Char, type Boolean is an ordinal type,
which means it has a fixed number of possible values that exist in a definite order. In
this order, False comes before True. By using the transfer function Ord you would
find that:
Ord(False) returns the value 0.
Ord(True) returns the value 1.
The status of the words True and False is a little tricky. In older versions of Pascal,
including both the Borland Pascals and Delphi, True and False are predefined identifiers
with no special status beyond that. The compiler predefines them as constants of
type Boolean. This means that if you really want to, you can give True and False
some other definition in your programs, as constants or variables. (This is a very
bad idea and I don’t recommend it!) When FreePascal is operating in Turbo mode
($MODE TP) or Delphi mode ($MODE DELPHI) this remains the case. However, in
FreePascal’s native mode ($MODE FPC) True and False are reserved words and may
not be redefined. Unless you explicitly specify one of the other modes, FreePascal
operates in FreePascal mode, so by default True and False are reserved words.
(Because True and False have not always been reserved words in Pascal, I’m not
placing them in uppercase as I do with other reserved words in this book.)
A Boolean variable occupies only a single byte in memory. The actual words
True and False are not physically present in a Boolean variable. When a Boolean
variable contains the value True, it actually contains the binary number 01. When
a Boolean variable contains the value False, it actually contains the binary number
00. If you write a Boolean variable to a disk file, the binary values 00 or 01 will be
physically written to the disk. However, when you print or display a Boolean variable
using Write or Writeln, the binary values are recognized by program code and the
words “TRUE” or “FALSE” (in uppercase ASCII characters) will be substituted for
the binary values 00 and 01.
Boolean variables are used to store the results of expressions using the relational
operators =, >, <, <>, >=, and <=, and the set operators +, *, -. Operators and
expressions will be discussed more fully in Section X.) An expression such as “2 <
3” is easy enough to evaluate; logically you would say that the statement “two is less
than three” is “true.” If this were put as an expression in Pascal, the expression would
return a Boolean value of True, which could be assigned to a Boolean variable and
saved for later processing:
OK := 2 < 3;
Data and Data Types 161
This assignment statement stores a Boolean value of True into the Boolean variable
OK. The value of OK can later be tested with an IF..THEN..ELSE statement, with
different actions taken by the code depending on the value assigned to Ok:
Ok := 2 < 3;
IF Ok THEN
Writeln(‘>>Two comes before three, not after!’)
ELSE
Writeln(‘>>We are all in very serious trouble...’);
Boolean variables are also used to alter the flow of program control in the
WHILE..DO statement and the REPEAT..UNTIL statement. (See Sections 9.5 and
9.6.)
Byte
Numeric type Byte is not present in Standard Pascal, although most microcomputer
implementations of Pascal now include it. Type Byte may be thought of as an
unsigned “half-precision” integer. It may express numeric values from 0 to 255.
Like Char, Byte is stored in memory as an eight-bit byte. On the lowest machine
level, therefore, Byte and Char are exactly the same. They only differ in what the
compiler will allow you to do with them.
Byte variables may not share an assignment statement with any type that is not
an integer type. Assigning a variable of type Byte with a variable or constant of
any of the real number types, or with Boolean, Char, or any other non-numeric
type will be flagged with a compile-time error. Type Byte may be freely included in
expressions with the other numeric types described in this section. Type Byte may
not, however, be assigned a numeric value of type Real, Single, Double, Extended,
Currency, or Comp.
162 FreePascal from Square One, Volume 1
Range errors
There is machinery inside FreePascal’s runtime library code to check whether the value
assigned to a variable is suitable for that variable. With numeric values, this is known as
range checking. If you attempt to assign a value to a numeric value and the value is out of
range for that variable, FreePascal’s runtime code can pop up an error message.
Range checking is possible both at compile time and at runtime. While the
compiler is building your executable program, it can spot certain obvious range
errors. For example:
VAR
ByteItem : Byte
ByteItem := -17
Variables of type Byte may not taken on negative values. If you try to assign a
negative constant to a variable of type Byte, you will get this message during the
compile process:
Warning: range check error while evaluating constants
This is not a runtime error, because FreePascal can tell during compilation that the
constant is negative, and it knows that type Byte cannot take a negative value. under
any circumstances. Now, assigning a “signed” variable (like Integer, ShortInt, or
LongInt) to Byte is perfectly safe unless (a) the signed variable contains a negative
value, or (b) the value in the signed variable is too large to fit in a value of type Byte.
The sorts of things that the compiler can spot at compile time tend to be few and
obvious. The truly gnarly problems will occur at runtime. Once your program hits
the silicon and begins to crunch, runtime range checking becomes important.
Note well: Runtime range checking is off by default. You have to explicitly turn it on to use
it. This is done by placing a compiler directive at the beginning of your program. The
compiler directive looks like a comment, and actually is a comment of a special sort:
{$ RANGECHECKS ON}
The dollar symbol tells the compiler that this particular comment is intended
for the compiler and not human programmers. Compiler directives are not actual
Pascal code and do not need to be followed by a semicolon. They’re instructions to the
compiler telling it how to generate the code for your program. There are a fair number
of them, though only a few will be useful to you while you’re learning Pascal.
Below is a short program that commits a runtime range error. The
$RANGECHECKS compiler directive is on. Read the program and see if you can
tell what’s wrong before continuing with the text:
Data and Data Types 163
1 PROGRAM runtimetest1;
2
3 {$RANGECHECKS ON}
4
5 VAR
6 I : Integer;
7 ByteItem : Byte;
8
9 BEGIN
10 I := -17;
11 ByteItem := I;
12 Writeln(ByteItem);
13 Readln;
14 END.
This time the FreePascal compiler will not detect the problem when you compile
your program. Difficulties will appear at runtime; that is, when you actually run the
program. Two things may happen, depending on whether you have enabled range
checking during the compilation of the program. If you ran the program from
within the Lazarus environment and range checking was on, a message box will
pop up that looks something like Figure 7.1.
If range checking was not on when you compiled the program, the program, in
essence, will punt. It will do its best to pour a signed value into an unsigned variable,
and what ends up in the variable depends on the physical bit-pattern of the negative
value. To put it mildly, such errors are unpredictable, and because they happen in
statements that work perfectly well most of the time, they can take a great deal of
time and head scratching to locate and fix.
In general, each numeric type has a defined range, and if you enable range
checking by using the {$RANGECHECKSON} compiler directive, assigning a
value outside that range to a variable will generate a runtime error. This applies to
the other numeric types discussed below as well as for type Byte.
Short integers
First cousin to type Byte is type ShortInt, a signed version of Byte. It may express
values between -128 and 127. The problems of range errors exist for ShortInt just
as they do for type Byte. ShortInt exists to provide a little bit of storage efficiency to
programs that use a lot of small, signed values. If you use a lot of numeric variables,
or (especially) large arrays of numeric variables, and can be sure the values will never
wander out of the range ‑128..127, you can save a lot of space by using ShortInt
variables instead of type Integer.
Bit 7 of a ShortInt is the sign bit. (See Figure 7.2.) If this bit is set to 1, the value is
considered to be negative.
There is no significant speed improvement to be had by using ShortInt over
larger numeric types, however. You might intuitively think physically small variables
would be operated on more quickly than larger ones, but this is not necessarily
the case. In fact, it takes post-8088 x86 CPU chips longer to process single-byte
quantities in Byte and ShortInt variables than it does to process 16-bit or even 32-
bit quantities. This is because Intel CPUs from the 286 back fetch and process data
in 16-bit chunks, while chips from the 386 on fetch and process data in 32-bit and
even (on 64-bit CPUs) 64-bit chunks. The newer CPUs must “stoop and grab” much
more often when data exists in 8-bit chunks, and hence take longer to do repetitive
operations. Use Byte and ShortInt to save space, not time!
Now, there are times when a 16-bit integer type is useful, especially when dealing
with legacy code that makes assumptions about the underlying sizes of numeric
types. This is a bad idea, but it was done a great deal in the past. (I wince a little when
I remember doing it myself.) To serve this need, FreePascal offers the SmallInt type,
which is a signed, 16-bit integer that can express values in the range -32768..32767.
Be careful not to confuse the ShortInt and SmallInt types, which is easy enough
to do when you’re just starting out.
Hi and Lo
There are circumstances where you may need to “pull apart” an integer type
and inspect portions of it individually. This is done using a pair of built-in
functions: Hi and Lo. The two functions essentially cut the memory space
of a numeric value in half, and return either the high or the low half. “High”
and “low” here refer to memory addresses, not values. As you might expect,
Hi returns the higher half of a value’s bytes as they reside in memory, and Lo
returns the lower.
Don’t confuse this with “cutting a value in half” in the sense of dividing it by
two. Look back to Figure 7.2. If you call the Hi function on a variable of type
Word, the function will return the higher of the value’s two bytes in memory.
If you call Hi on a variable of type LongWord, the function will return the
variable’s two bytes that are highest in memory. If you call Hi on a variable of
type QWord, the function will return the value’s four bytes that lie the highest
in memory. The Lo function does the same thing, only returning the half of a
value that lies lowest in memory.
A quick example: Given the 16-bit integer value 17,353 (hexadecimal equivalent
$43C9), Hi(17353) will return 67 (hex $43) and Lo(17353) will return 201 (hex
$C9). I include the hexadecimal equivalents because Hi and Lo are most typically
used in system programming, much of which is documented and discussed using
hexadecimal values.
Note that in using Hi and Lo on signed values, the sign bit is treated as just
another bit in the high byte returned by Hi, and will not cause the value returned by
either Hi or Lo to be returned as a negative quantity. For example, given the negative
integer constant -21,244 (hex $AD04), Hi(-21244) will return 173 (hex $AD), and
Lo(-21244) will return 4 (hex $04). Hi and Lo never return negative values.
If you’re a newcomer and still a little fuzzy on the notion of Pascal functions, I’ll
be covering them in detail in Section 10.1.
7 comes 8, and so on, with no possible values in between. SmallInt is also a scalar
type, since its values are numeric values, and not some other symbolic constants
encoded internally as numeric values, as are Boolean’s True and False. All scalars
are ordinals, but only ordinal types expressing numeric values are scalars.
Scalar types have absolute precision; that is, the value of the integer 6 is exactly
six. (I have a marvelously ironic button reading, “2+2 = 5...for large values of 2.”)
Computing with values obtained from the real world demands a way to
deal with fractions. So Standard Pascal supports type Real, which can express
numbers with fractions and exponents. Numbers like this are known as rational
numbers in mathematics. In computing they are more often called real numbers,
and are directly expressed in type Real. Real numbers, especially very large ones
or very small ones, do not have absolute precision. For example, the scientific
notation value 1.6125 X 1010 is expressed in Pascal as 1.6125E10. The value is
a real number having an exponent. You might expand the exponent and write
it as 16,125,000,000. 1 This notation implies that we know the value precisely.
However, we do not. A real number offers a fixed number of significant figures
and an exponent giving us an order of magnitude, but there is a certain amount
of “fuzz” in the actual value. The digits after the 5 in 16,125,000,000 are zeroes
because we don’t know what they really are. The measurements that produced the
number were not precise enough to pin down the last six digits—so they are left
as zeroes to express the order of magnitude that is expressed by the exponential
in the form 1.6125E10.
Real number types cannot be scalar types due to this lack of absolute precision.
They are “real” in that they are usually used in the scientific and engineering
community to represent physical measurements made of things in the real world.
Integer types, by contrast, are largely mathematical in nature, and express abstract
values usually generated by logic or calculation and not by physical measurement
out here in the real world.
Real numbers may be expressed two ways in FreePascal. One way, as we’ve seen,
is with an adaptation of scientific notation: a mantissa (in the example above, 1.6125)
giving the significant figures, and an exponent (E10) giving the order of magnitude.
This form is used for very large and very small numbers. For very small numbers,
the exponent would be negative: 1.6125E-10. You would read this number as “One
point six one two five times ten to the negative tenth.”
The second and more familiar way to express a real number is with a decimal
point: 121.402, 3.14159, 0.0056, -16.6, and so on. Because using long strings of zeroes
is inconvenient and an invitation to error (Count the zeroes!) decimal notation like
this is best used for relatively small numbers.
Data and Data Types 169
You’ll see the term “floating point” a lot in programming work, and it refers
to the fact that the decimal point in a real number is not required to be at any
particular place within however many significant figures a real number might have.
For example, in a floating-point value with the six significant figures of accuracy
123456, the decimal point can be anywhere: 1.23456, 12.3456, 123.456, 1234.56, or
12345.6.
If this still isn’t completely clear, compare these floating point examples to the
Currency type, which is a “fixed-point” real, in which the decimal point is always
placed four significant figures from the low end. This allows the expression of
financial values accurate to a tenth of a mill. (A mill is one thousandth of a cent, and
not often mentioned outside of financial circles.) More on Currency later.
RS := 9.144E35; RD := 8.66543E255;
RS := ((RS*RS)*RD)/9.95E306;
VAR
BigNum : Comp;
TestComp := 17284;
Writeln(TestComp); { Displays as 1.72840000000000E+0004 }
Writeln(TestComp:7); { Displays as 1.7E+0004 }
Writeln(TestComp:7:2); { Displays as 17284.00 }
Writeln(TestComp:7:0); { Displays as 17284 }
Currency is related to Comp, and the two types both occupy 8 bytes in memory.
Currency has the same precision as Comp, but its range is less, because Currency
values are assumed to have a decimal point and four places of precision after the
decimal. In a sense, Currency is a “fixed-point” real number with complete precision
throughout its range, which needs a whole line to state completely:
-$922,337,203,685,477.5808 to $922,337,203,685,477.5807
This is just short of a quadrillion dollars, which should keep financial analysts happy
for at least a little while. We hope.
Currency’s issues
As good as Currency sounds, there are some issues in using it. As with Comp, on
CPUs containing an Intel x87 FPU Currency is handled by the FPU. (On CPUs without
an x87 FPU, Currency cooks down to type Int64.) It’s hard to explain in detail in
an introductory book like this, but there are conversion problems when converting
values stored in a Currency variable to a value stored in one of the floating-point
types. It’s best to keep values in the Currency type as much as possible during your
calculations. In particular, do not attempt to use the absolute value function Abs
with Currency. Behind the scenes, Abs converts the Currency value to Extended
and then back again, with rounding errors as an unintended consequence. (This
occurs only on CPUs containing an x87 FPU.)
The other issue is a difference in rounding methods between Delphi and
FreePascal. There are two types of rounding in financial calculation. One is the
method most of us learned in grade school, in which a value like 65.285 would
be rounded up to 65.29. The other is “bankers’ rounding,” in which numbers like
65.285 would be rounded to the closest even number, which in this example would
be 65.28. (Thanks to Mike Riley for pointing this out to me.)
The Delphi runtime library uses bankers’ rounding for Currency values, whereas
FreePascal’s implementation of Currency uses conventional rounding. If you’re
bringing Delphi code over to Lazarus/FreePascal, this may become a problem.
Currency, like Comp, has special issues with conversion to text for display. I’ll
deal with that matter later in this book.
174 FreePascal from Square One, Volume 1
Chapter 8.
Derived Types and
Data Structures
A ll the types we’ve discussed up to this point have been simple types, predefined
by FreePascal and ready to use. Much of the power of Pascal lies in its ability to
create more complex structures of data out of these simple types. Derived types and
data structures can make your programs both easier to write and, later on, easier to
read as well.
We touched on this earlier in the book. Recapping: Creating custom data types is
easy to do. The reserved word TYPE begins the type definition part of your program,
and that’s where you lay out the plan of your data structures:
TYPE
YourType = ItsDefinition;
In general terms, a type definition consists of the name of the type, followed by an
equal sign, followed by the definition of the type. From now on, many of the types
we’re going to discuss must be declared and defined in the type definition part of
your program. Once defined, you can declare a variable of your “custom” type in
the variable declaration section of your program:
VAR
ANewVariable : YourType;
A custom type of this sort is often called a derived type in Pascal circles.
A type definition does not, by itself, occupy space in your executable program
file’s data area, as variables do. What a type definition provides are instructions to the
compiler telling it how to deal with variables of type YourType when it encounters
them further down in your program source file.
With some few exceptions, (strings and subranges, for example) you cannot write
derived or structured types to your display or printer with Write or Writeln. If you
want to display derived or structured types somehow, you must write procedures
specifically to display some representation of the type on your display or printer.
175
176 FreePascal from Square One, Volume 1
Now, to create a variable to hold grades, you would declare a variable this way:
History : Grade;
TYPE
SemesterGrades = RECORD
StudentID : String[9];
SemesterID : String[6];
Math : Grade;
English : Grade;
Drafting : Grade;
History : Grade;
Spanish : Grade;
Gym : Grade;
SemesterGPA : Real
END;
Now you can define a Pascal variable as having the type SemesterGrades:
VAR
ThisSemester : SemesterGrades;
By a single variable name you now can control nine separate data chunks (which,
when part of a record type, are called fields) that you would otherwise have to deal
with separately. This can make certain programming tasks a great deal simpler. But
even more important, it allows you to treat logically-connected data as a single unit
to clarify your program’s design and foster clear thinking about its function.
For example, when you need to write the semester’s grades out to a disk file, you
needn’t fuss with individual subjects separately. The whole record goes out to disk
at once, as though it were a single variable, without any reference to the individual
fields from which the record is built. The alternative is a series of statements that
write the student ID to disk, followed by the semester ID, followed by the math
grade, followed by the English grade, and so on.
One caution about this particular example: If you’ve ever worked with a database
manager, storing Pascal records in a Pascal file is very close to what a database manager
does when it writes individual database records to a database table. FreePascal can
work very effectively with external database engines like MySQL or SQLite, so writing
records out as simple files is not done much anymore. You simply need to be aware
that it can be done. Data management these days requires a “real” database engine.
You don’t have to write an application-specific database manager yourself!
Pascal records really aren’t about writing groups of variables to disk. It’s about
how you think about the problem at hand: When you need to think of all of a
student’s grades taken together, you can think of them as a unit, in the form of a
record. When you need to deal with them separately, Pascal has a simple way of
picking out any individual field within the record for individual attention. Selecting
one field out of a record is done this way:
178 FreePascal from Square One, Volume 1
MyMath := ThisSemester.Math;
You simply specify the record name followed by the field name, separated by a period
character. ThisSemester.Math is in a sense an expression that “cooks down” to a
single value of type Grade. Informally, this is sometimes called “dotting.”
How you think of the data now depends on how you need to think of the data.
Pascal encourages you to structure your data in ways like this that encourage clear
thinking about your problem at a high level (all grades taken together) or at a low
level (each grade a separate data item.)
Much of the skill of programming in Pascal is learning how to structure your
data so that details are hidden by the structure until they are needed. It’s much like
being able to step back and see your data as a forest without being distracted by the
individual trees.
Like most tools, the structuring of data is an edge that cuts two ways. It is all
too easy to create data structures of Byzantine complexity that add nothing to a
program’s usefulness while obscuring its ultimate purpose. If the data structure
you create for your program makes the program harder to understand from “three
steps back,” you’ve either done it the wrong way, or done it too much.
The rule of thumb I use is this: Don’t create data structures for data structure’s sake.
Unless there’s a reason for it, resist. Simplicity doesn’t necessarily sacrifice power
or flexibility.
Subranges in Detail
If you choose any two legal values in an ordinal type, those two values plus all values
that lie between them define a subrange of that ordinal type. For example, these are
subranges of type Char:
TYPE
Uppercase = ‘A’..’Z’;
Lowercase = ‘a’..’z’;
Digits = ‘0’..’9’;
you would have, instead, a subrange of type Integer. ‘7’ is not the same as 7!
Derived Types and Data Structures 179
An expression in the form ‘A’..’Z’ or 3..6 is called a closed interval. A closed interval
is a range of ordinal values including the two stated boundary values and all values
falling between them. We’ll return to closed intervals later on in this chapter while
discussing sets.
TYPE
Spectrum = (Red, Orange, Yellow, Green, Blue, Indigo, Violet);
The values list of an enumerated type definition is always given within parentheses.
The order you place the values within the parentheses defines their ordinal value,
which you can test using the Ord(X) function. For example, Ord(Yellow) would
return a value of 2. Ord(Red) would return the value 0. Ord(Indigo) would return
the value 5.
You can compare values of an enumerated type with other values of that same
type. It may be helpful to substitute the ordinal value of enumerated constants for
the words that name them when thinking about such comparisons. The statement
Yellow > Red (think: 2 > 0) would return a Boolean value of True. Green > Violet or
Blue < Orange would both return Boolean values of False. Comparisons between
enumerated types is about their position in a number line, not their names!
Individually, the values of type Spectrum are all considered named constants.
They may be assigned to variables of type Spectrum. For example:
180 FreePascal from Square One, Volume 1
VAR
Color1, Color2 : Spectrum;
Color1 := Yellow;
Color2 := Indigo;
You cannot, however, assign just anything to one of the values of type Spectrum.
Statements like Red := 2 or Red := Yellow make no sense and will not be accepted
by the compiler.
Enumerated types may index arrays. (I’ll be discussing arrays in detail a little
later in this chapter.) For example, each color of the spectrum has a frequency and
wavelength associated with it. These frequencies could be stored in an array, indexed
by the enumerated type Spectrum:
The functions Ord and Odd work with enumerated types, as do the Succ and
Pred functions. This is due to an enumerated type’s having a fixed number of
elements in a definite order that does not change. Succ(Green) will always return
the value Blue. Pred(Yellow) always returns the value Orange. Be aware that
Pred(Red) and Succ(Violet) are undefined. There is no value before Red or after
Violet. You should test for the two ends of the Spectrum type while using Succ
and Pred to avoid assigning an undefined value to a variable.
Enumerated types may also be control variables in FOR/NEXT loops. (These
will be discussed in detail in the next chapter.) In continuing with the example
begun above, we might calculate the frequencies of light for each of the colors of
type Spectrum this way:
your program will display the ASCII word “Orange” in the console window. This has
not always been the case. Turbo Pascal used to flag this with the following error:
FreePascal, by contrast, is more than happy to display or print the enumerated type
values as you defined them. There’s some potential for confusion here, however:
Internally, the values of enumerated types are maintained as binary numbers, not
ASCII strings like “Green.”
VAR
CharSet : SET OF Char;
CharSet := [‘A’,’Q’,’W’,’Z’];
A pair of square brackets when used to define a set (as shown above) is called a set
constructor.
Pascal sets are very useful, often in non-obvious ways. For example, sets provide
an easy way to sift valid user responses from invalid ones. In answering even a
simple, yes/no question, a user may in fact type two equally valid single characters
for yes, and two for no: Y/y and N/n. Ordinarily, you would have to test for each one
individually:
IF (Ch=’Y’) OR (Ch=’y’) THEN DoSomething;
The operator IN tests whether the value stored by Ch is present in the set. IN returns
True if an element is present in a set, or False if an element is not in the set.
VAR
LowColors : SET OF Spectrum;
LowColors := [Red,Orange,Yellow];
Only one numeric type may be a set base type: Byte. The types ShortInt and
larger numeric types do not qualify. Subranges may be the answer: If you define
a numeric subrange spanning 256 or fewer values, you may define a set with that
subrange type as the set’s base type:
TYPE
ShoeSizes = 5..17;
VAR
SizesInStock : SET OF ShoeSizes;
In addition to establishing the elements present in a set in the VAR and TYPE
sections of your program, you may also assign a range of elements to a set in an
assignment statement, assuming that the elements assigned are of an acceptable
base type:
VAR
Uppercase, Lowercase, Whitespace, Controls : SET OF Char;
Uppercase := [‘A’..’Z’];
Lowercase := [‘a’..’z’];
Controls := [Chr(1)..Chr(31)];
Whitespace := [Chr(9),Chr(10),Chr(12),Chr(13),Chr(32)];
This is certainly easier than explicitly naming all the characters from A to Z to assign
them to a set. A range of elements containing no gaps (like ‘A’..’Z’) is called a closed
Derived Types and Data Structures 183
interval. The list of members within the set constructor can include single elements,
closed intervals, and expressions that yield an element of the base type of the set. These
must all be separated by commas, but they do not have to be in any particular order:
X : Byte;
X := 77;
GradeSet := [‘A’..’F’,’a’..’f’];
BadChars := [Chr(1)..Chr(8),Chr(11),Chr(X+4),’Q’,’x’..’z’];
You should take care that expressions do not yield a value that is outside the range
of the set’s base type. If X in the BadChars type defined above grows to 252 or
higher, the result of the expression Chr(X+4) will no longer be a legal character.
The results of such an expression will be unpredictable, other than to say they won’t
do you very much good.
Sets like Uppercase, Lowercase, and Whitespace defined above can be very useful
when manipulating characters typed at the keyboard or from another unpredictable
source. Here are some simple functions making use of these character sets:
BEGIN
IF Ch IN Lowercase THEN CapsLock := Chr(Ord(Ch)-32)
ELSE CapsLock := Ch
END;
BEGIN
IF Ch IN Uppercase THEN DownCase := Chr(Ord(Ch)+32)
ELSE DownCase := Ch
END;
BEGIN
IsWhite := Ch IN WhiteSpace
END;
VAR
NullSet = SET OF Spectrum;
Set operators
The IN operator we used above is not the only operator available for use with sets.
There are two whole classes of set operators in FreePascal: Operators that build
sets from other sets, and operators that test relationships between sets and return a
Boolean result.
Pascal allows you to create new sets from existing sets using several operators. To
a great extent the operators follow the rules of set arithmetic you may have learned
in grade school. Table 8.1 summarizes the set operators implemented in FreePascal.
All of them take set operands and return set values. Note that Include and Exclude
are implemented as functions in FreePascal, so although they act like operators,
they look like functions that return set types.
Set union
The union of two sets is the set that contains all members contained in both sets. In
simpler terms, it means combining the two sets into a single set. The symbol for the
set union operator is the plus sign (+), just as for arithmetic addition. An example:
VAR
SetA, SetB, SetX, SetY, SetZ : SET OF Char;
After the set union operation, SetZ contains ‘Y’, ‘y’, ‘N’, ‘n’, ‘M’, and ‘m’. Note that
although ‘M’ and ‘m’ exist in both SetX amd SetY, each appears only once in the
union of the two sets. A set merely says whether or not a member is present in the
set; it is meaningless to speak of how many times a member is present in a set. By
definition, each member is present only once, or not present at all.
Set difference
The “difference” of two sets is conceptually related to arithmetic subtraction. Given
two sets SetA and SetB, the difference between them (expressed as SetA - SetB) is
the set consisting of the elements of SetA that remain once all the elements of SetB
have been removed from it. For example:
SetA := [‘Y’,’y’,’M’,’m’];
SetB := [‘N’,’n’,’M’,’m’];
SetX := SetA - SetB;
SetX now contains ‘Y’, ‘y’. Conceptually, set difference “pulls out” whatever SetB
contains that is also present in SetA. SetB does not have to be a subset of SetA. In
other words, SetB may contain elements that are not present in SetA.
Set intersection
The intersection of two sets is the set that contains as members only those members
contained in both sets. The symbol for set intersection is the asterisk (*), just as for
arithmetic multiplication. For example:
SetX := [‘Y’,’y’,’M’,’m’];
SetY := [‘N’,’n’,’M’,’m’];
SetZ := SetX * SetY;
SetZ now contains ‘M’ and ‘m’, which are the only two members that are contained
in both sets.
186 FreePascal from Square One, Volume 1
SetX := [‘Y’,’y’,’M’,’m’,’C’,’c’];
SetY := [‘N’,’n’,’C’,’c’];
SetZ := SetX >< SetY;
Here, the intersection of SetX and SetY is the set [‘C’,’c’]. The union of the two
sets is [‘Y’,’y’,’M’,’m’,’N’,’n’,’C’,’c’]. If you remove the intersection of the two
sets from the union of the two sets, what you have left (the symmetric difference) is
[‘Y’,’y’,’M’,’m’,’N’,’n’].
TYPE
Spectrum = (Red, Orange, Yellow, Green, Blue, Indigo, Violet);
VAR
MyColors : SET of Spectrum; { MyColors is created as a null set! }
VAR
ColorToBeRemoved : SET of Spectrum;
ColorToBeRemoved := [Green];
Colors := Colors – ColorToBeRemoved;
Similarly, the Include function adds a single element to a set, assuming the
element isn’t in the set already:
VAR
CharSet,JustR : SET OF Char;
CharSet := [‘A’,’Q’,’W’,’Z’];
Include(Charset,’R’);
JustR := [‘R’];
Charset := Charset + JustR;
VAR
SetX : SET OF Char;
SetQ : SET OF Spectrum;
OK : Boolean;
This holds true for all set relational operators, not just equality as demonstrated
above.
The single most important set relational operator is the element inclusion
operator IN. IN tests whether a value of a set’s base type is a member of that set.
VAR
Ch : Char;
As described earlier, this example provides a clean and easy way to tell whether
a user has typed the letter Y (upper or lower case) in response to a prompt. The
IN operator tests whether the typed-in character is a member of the set constant
[‘Y’,’y’]. (IN works just as well with set variables.)
The greater than (>) and less than (<) operators make no sense when applied
to sets, because sets have no implied order to their values. However, there are two
additional set relational operators that make use of the same symbols as used by the
greater than or equal to (>=) and less than or equal to (<=) operators. These are the
set inclusion operators.
Inclusion of left in right (<=) tests whether all members of the set on the left
are included in the set on the right. Inclusion of right in left (>=) tests whether all
members of the set on the right are included in the set on the left. The action these
two operators take is identical except for the orientation of the two operands with
respect to the operator symbol. Given two sets, Set1 and Set2, this expression:
(Set1 <= Set2) = (Set2 >= Set1)
VAR
Vowels,Alphabet,Samples : SET OF Char;
Vowels:=[‘A’,’E’,’I’,’O’,’U’];
Alphabet:=[‘A’..’Z’]; { Set of all UC letters }
Samples:=[‘A’,’D’,’I’,’Q’,’Z’];
In addition to demonstrating the set inclusion operators, these examples also show
some uses of the AND and NOT logical operators in IF statements, which I’ll
explain in detail in the next chapter. Given the elements assigned to Samples in the
above example, this output will be displayed:
Some or all samples are uppercase letters.
Now, as practice before going on, jot down two sets of characters that would
trigger the other two messages in the above example.
Both Vowels and Alphabet are sets of characters. To test whether Vowels is a
subset of Alphabet, you need the left-in-right set inclusion operator:
Vowels >= Alphabet
This will compile, and simply tests whether everything in the Vowels set is also
present in Alphabet. With the values given earlier, the expression evaluates to
True.
Note: For space reasons I have chosen not to discuss the binary representation of
sets in this book.
190 FreePascal from Square One, Volume 1
8.4. Arrays
Sometimes it’s not enough to work on a single data item, whatever its type.
Sometimes you need to work on a whole row of them. If all the data items in the row
are all of the same type, you can refer to them by number, just as you might choose
photograph #6 from an old roll of film, or the legendary Love Potion #9. Pascal can
do that, using data structures called arrays.
An array is a data structure consisting of a fixed number of elements of the same
type, with the whole data structure given a single identifier as its name. The program
keeps track of individual elements by number. Sometimes you name the entire
array to work with it as a unified whole. Most of the time you identify one of the
individual elements, by number, and work with that element alone. The number
identifying an array element is called an index. In Pascal, an index need not always
be a traditional number. Enumerated types, characters, and subranges may also act
as array indices.
It may be helpful to think of an array as a row of identical empty boxes in
memory, side by side. The program allocates space for the boxes, but it is your job as
programmer to fill them and manipulate their contents. The elements in an array are,
in fact, set side-by-side in order in memory. (You don’t need to know how arrays are
laid out in memory in order to use them during ordinary Pascal programming.)
An array element may be of any data type except a file. Arrays may consist of
data structures; that is, you may have arrays of records and arrays of arrays. An
array index must be a member or a subrange of an ordinal type, or a programmer-
defined enumerated type. Floating point numbers may not act as array indices, nor
may LongInt or Comp. Type Integer, ShortInt, Byte, Word, Char, and Boolean
may all index arrays.
Below are some valid array declarations, just to give you a flavor of what’s possible.
(If you don’t understand for now what records or strings are, bear with me for the
time being. They’re up next!)
CONST
Districts = 14;
TYPE
String80 = String[80];
Grades = ‘A’..’F’; { Subrange }
Percentile = 1..99; { Ditto }
{ Enum. type }
Levels = (K,G1,G2,G3,G4,G5,G6,G7,G8,G9,G10,G11,G12);
{ Ditto }
Subjects = (English,Math,Spelling,Reading,Art,Gym);
Derived Types and Data Structures 191
Profile = RECORD
Name : String80;
SSID : String80;
IQ : Integer;
Standing : Percentile;
Finals : ARRAY[Subjects] OF Grades
END;
VAR
K12Profile : ARRAY[Levels] OF Profile;
Passed : ARRAY[Levels] OF Boolean;
Subtotals : ARRAY[1..24] OF Integer;
AreaPerc : ARRAY[1..Districts] OF Percentile;
AreaLevels : ARRAY[1..Districts] OF ARRAY[Levels] OF Percentile;
RoomGrid : ARRAY[1..3,Levels] OF Integer;
The declarations shown above are part of an imaginary school district records
manager program written in Pascal. Note that Passed is an array whose index is an
enumerated type. Passed[G5] would contain a Boolean value (TRUE or FALSE)
indicating whether a student had passed or failed the fifth grade. Remember, the
identifier G5 is not a variable; it is a constant value, one value of the enumerated
type Levels.
The low limit and high limit of an array’s index are called its bounds. The bounds
of a static array in Pascal must be fixed at compile time. The FreePascal compiler
must know, when it compiles the program, exactly how large all data items are
going to be. FreePascal supports a separate type of array called a dynamic array,
which gives you more flexibility when defining arrays. I’ll talk about dynamic arrays
a little later.
With this in mind, the variable AreaPerc deserves a closer look. At first glance,
you might think it has a variable for a high bound, but actually, Districts is a constant
with a value of 14. Writing [1..Districts] is no different from writing [1..14], but
within the context of the program it assists your understanding of what the array
actually represents.
192 FreePascal from Square One, Volume 1
Multidimensional arrays
Figure 8.1. Multidimensional Arrays
Most of the arrays shown in the earlier example are one-dimensional. A one-
dimensional array has only one index. A two-dimensional array has two indices. A
three-dimensional array has three, and so on. (See Figure 8.1.) An array may have
any number of dimensions, but it edges toward bad practice to define an array with
more than two or (on the outside) three. Also, the more dimensions an array has,
the larger it tends to be and the more memory it uses—and the more likely that parts
of it are empty or full of duplicate or rarely-accessed information that does nothing
for you but waste memory. There are better, less memory-wasteful ways to handle
large, complicated data structures. The old-school solution is linked lists, which I
will not cover in this book. Dynamic arrays are another, more modern solution to
that problem, and I’ll cover those in the next section.
Derived Types and Data Structures 193
When FreePascal allocates an array in memory, it does not zero the elements of
the array, as BASIC would. In general, Pascal does not initialize data items of any
type for you. If there was garbage in RAM where the compiler went out to set up
an array, the array elements will contain the garbage when the array is allocated. If
you wish to zero out or otherwise initialize the elements of an array, you must do
it yourself, in your program, before you use the array. This is not difficult, using a
FOR loop (see Section 9.4 for more on FOR loops):
FOR I := 1 TO 24 DO Subtotals[I] := 0;
Good examples of arrays used effectively can be found in the ShellSort procedure
in Section 10.3 and the QuickSort procedure in Section 10.6.
VAR
LuminanceReadings : ARRAY of Integer;
194 FreePascal from Square One, Volume 1
FreePascal understands that this is a dynamic array because no bounds are declared
for it. Until your code specifies how many elements are in the array, the array occupies
no space in memory and cannot be used. You specify the number of elements in the
array with a predefined procedure SetLength:
SetLength(LuminanceReadings, 1000);
This procedure call allocates dynamic memory for 1000 integers and associates
that block of memory with the variable name LuminanceReadings. Once you’ve
allocated memory with SetLength, you can use the dynamic array as you would
use any array of 1,000 integers. There are some restrictions, the most important
of which is this: The array index begins at 0. The first element of any dynamic array
is always element 0. There’s no way for you to set the index of the first element to
1 or any other value.
Dynamic arrays are managed by the runtime code that FreePascal links into your
program. When the array goes out of scope (see Chapter 13 for more on scope,
which I haven’t covered yet) the runtime code automatically deallocates the array’s
memory on the heap.
There’s another, subtler restriction that won’t make complete sense until you
learn more about dynamic memory and the heap. If you declare the names of two
dynamic arrays and allocate one, you can assign the second identifier to the first,
like this:
VAR
CrateWeights1, CrateWeights2 = ARRAY of Integer;
SetLength(CrateWeights1, 50);
As you would expect, the two arrays now contain exactly the same elements. But
there’s a very serious catch: Both identifiers now refer to the same array in dynamic memory.
You only allocated memory for one array, and one is all there is. By assigning a
second array to the first, in effect you’re giving the allocated array a second name. If
you change elements in CrateWeights2, those changes will be made to the elements
of CrateWeights1 as well, and vise versa.
This may seem bizarre until you understand the concept of Pascal pointers,
which I can’t explain in detail in this book. A pointer is basically a memory address.
CrateWeights1 and CrateWeights2 are in reality two pointers that point to the
Derived Types and Data Structures 195
This allocates an entirely new duplicate array on the heap, and once that happens,
changes made to CrateWeights1 will not affect CrateWeights2 and vise versa.
Likewise, if you call SetLength a second time with CrateWeights2 as the first
parameter, you will allocate a second, independent dynamic array somewhere
else in memory. This time, the elements of CrateWeights1 will not be duplicated,
and CrateWeights2 will contain “zero-filled” data until you assign data to it. and
making changes to one will have no effect on the other. Note well: It’s not a good
idea to assume that a new dynamic array—or any other new data item—will be
automatically zero-filled by the runtime code. If you need to rely on having an array
full of zeroes, assign zeroes to all the elements before you begin using it!
Dynamic arrays can be resized by making a call to SetLength with a different
number of elements. Do not assume that the array will contain the same values after
a second call to SetLength. Always assume that SetLength hands you a brand new
array containing undefined values.
You can deallocate a dynamic array at any time by assigning the predefined value
NIL to the array:
LuminanceReadings := NIL;
The runtime code will return the space formerly occupied by the array to the heap,
so it can be used again for other dynamic variables.
There’s one final weirdness about dynamic arrays: In a dynamic array having
100 elements, for example, you should not access element 100. There is no element
100. Because dynamic array indexes are always 0-based, the indexes in a 100-
element array run from 0-99. If you attempt to read a value at index 100, you will
get either a garbage value or a value belonging to some other dynamic variable
entirely.
8.5. Records
An array is a data structure composed of a number of identical data items all in
196 FreePascal from Square One, Volume 1
a row and referenced by number. This sort of data structure is handy for dealing
with large numbers of the same type of data; for example, values returned from an
experiment of some sort. You might have a collection of five hundred temperature
readings and need to average them and perform analysis of variance on them. The
easiest way to do that is load them into an array and work with the temperature
readings as elements of the array.
There is a data structure composed of data items that are not of the same type.
It’s called a record, and it gets its name from its origins as one line of data in a data
file.
A record is a structure composed of several data items of different types grouped
together. These data items are called the fields of the record.
Let’s work out a short and much-simplified conceptual example. An auto-repair
shop might keep a file on its spare parts inventory. For each part they keep in stock,
they need to record its part number, its description, its wholesale cost, retail price,
customary stock level and current stock level. All these items are intimately linked
to a single physical gadget, (a car part) so to simplify their programming the shop
puts the fields together to form a record:
TYPE
PartRec = RECORD
PartNum, Class : Integer;
PartDescription : String;
OurCost : LongInt;
ListPrice : LongInt;
StockLevel : Integer;
OnHand : Integer;
END
VAR
CurrentPart, NextPart : PartRec;
PartFile : FILE OF PartRec;
CurrentStock : Integer;
MustOrder : Boolean;
The entire structure becomes a new type with its own name. Data items of the
record type can then be assigned, written to files, and otherwise worked with as a
single entity without having to explicitly mention all the various fields within the
record.
When you need to work with the individual fields within a record, the notation
consists of the record identifier followed by a period (“.”) followed by the field
identifier:
CurrentStock := CurrentPart.OnHand;
IF CurrentStock < CurrentPart.StockLevel
THEN MustOrder := True;
Accessing individual fields within a record this way is informally called “dotting.”
Relational operators may not be used on records. To say that one record is “greater
than” or “less than” another cannot be defined since there are an infinite number of
possible record structures with no well-defined and unambiguous order for them
to follow. In the example above we are comparing the fields of two records, not the
records themselves. The fields are both integers and can therefore be compared by
the “<” operator.
Note the repetition of the record name before the field name. That’s logically
unnecessary, if we know we’re just going to be accessing several fields from the
same record in quick succession here. If you have to go down a list of fields within
the same record and work with each field, you can avoid specifying the identifier
of the record each and every time by using a special statement called the WITH
statement.
We could simplify assigning values to several fields of the same record by writing
the above snippet of code this way:
WITH CurrentPart DO
BEGIN
OurCost := 1075;
ListPrice := 4185;
OnHand := 4;
END;
The space between the BEGIN and END is the “scope” of the WITH statement.
Within that scope, the record identifier need not be given to work with the fields
198 FreePascal from Square One, Volume 1
of the record named in the WITH statement. (If you are very new to Pascal, you
might return to this section after reading the general discussion on statements in
Section 9.1. WITH statements are subject to the same rules that all types of Pascal
statements obey.)
WITH statements need not have a BEGIN/END unless they contain more than
a single statement. A WITH statement may include only a single statement to work
with a record if that single statement contains several references to fields within a
single record:
WITH CurrentPart DO VerifyCost(OurCost,ListPrice,Check);
In this example, VerifyCost is a procedure that takes as input two price figures
and returns a value in Check. Without the WITH statement, calling VerifyCost
would have to be done this way:
VerifyCost(CurrentPart.OurCost,CurrentPart.ListPrice,Check);
The WITH statement makes the statement crisper and much easier to understand.
By using WITH, we can hide the unnecessary details of what larger record we’re
working with during the time that we’re working on a small-scale with the fields of
that record. There’s no need to “see” the name of the embracing record every time
we access a field, so the smart thing to do is put the name of the record off to one
side so it doesn’t get in the way. That’s the spirit in which WITH was created.
Nested Records
A record is a group of data items taken together as a named data structure. A record is
itself a data item, and so records may themselves be fields of larger records. Suppose
the repair shop we’ve been speaking of expands its parts inventory so much that
finding a part bin by memory gets to be difficult. There are ten aisles with letters
from A through J, with the bins in each aisle numbered from 1 up. To specify a
location for a part requires an aisle character and a bin number. The best way to do
it is by defining a new record type:
TYPE
PartLocation = RECORD
Aisle : ‘A’..’J’;
Bin : Integer
END;
Since each part has a location, type PartRec needs a new field:
TYPE
PartRec = RECORD
PartNum, Class : Integer;
Derived Types and Data Structures 199
PartDescription : String;
OurCost : LongInt;
ListPrice : LongInt;
StockLevel : Integer;
OnHand : Integer;
Location : PartLocation { A record! }
END;
Location is “nested” within the larger record. To access the fields of Location you
need two periods:
LookAisle := CurrentPart.Location.Aisle
WITH statements are fully capable of handling many levels of record nesting.
You may, first of all, nest WITH statements one within another. The following
compound statement is equivalent to the previous statement:
WITH CurrentPart DO
WITH Location DO LookAisle := Aisle;
The WITH statement also allows a slightly terser form to express the same
thing:
WITH CurrentPart, Location DO LookAisle := Aisle;
For this syntax you must place the record identifiers after the WITH reserved
word, separated by commas, in nesting order. That is, the name of the outermost
record is on the left, and the names of records nested within it are placed to its right,
with the “innermost” nested record placed last.
Records were originally essential in their use as “slices” of a disk file. This is much
less useful than it once was, with simple databases like SQLite shipped free with
FreePascal and Lazarus. I won’t be discussing file I/O in this book for that reason: It’s
been largely subsumed by database technology.
Records lost even more of their usefulness when objects were added to Pascal
with Turbo Pascal 5.5. Objects resemble records, but may contain procedures and
functions as well as data. (Objects have other, subtler tricks too, and I really can’t
cover them in this introductory book.)
One final note on records: from its original definition Pascal has supported
“variant records,” a feature that allows a record to have two or more different
200 FreePascal from Square One, Volume 1
structures depending on the value of a field in the record. I’ve dropped the description
of variant records from this book because almost no one uses them anymore. An
advanced Pascal feature called variants broadens the concept beyond records, as
does a feature of object-oriented programming called polymorphism. Both of these
are advanced topics that I hope to cover in a brand new book at some point.
If you find you need a solid explanation of variant records, copies of my 1993
print book Borland Pascal 7 from Square One treats them at length and can be had on
the used book markets.
8.6. Strings
Manipulating words and lines of text is a fundamental function of a computer
program. At minimum, a program must display messages like “Press RETURN to
continue:” and “Processing completed.” In Pascal, as in most computer languages, a
line of characters to be taken together as a single entity is called a string.
TYPE
PAOC25 = PACKED ARRAY[1..25] OF Char;
The second string constant is two characters short of 25 long, so the compiler
will display an error message.
The second string constant could not be considered a PAOC25 because it is
only 23 characters long; hence the type mismatch error. Of course, you could have
padded out the second constant with spaces, and the padded constant would have
been acceptable.
You can compare two PAOC-type strings with the relational operators, read and
write them from files, and print them to the screen. And that’s where it ends. All
other manipulations have to be done on a character-by-character basis, as though
the string were just another array of any simple type.
Note well: PAOC-type strings are now decades obsolete. The only time to use them
is in situations where you are forced to deal with code in extremely old programs
and have to add features or fix bugs.
Short strings
All modern Pascal implementors, including those who wrote Turbo Pascal and
FreePascal, have implemented character strings as what are called variable-length
strings. Like PAOC-type strings, variable-length strings are arrays of characters, but
they are treated by the compiler in a special way. There are several distinct varieties
of variable-length strings in FreePascal, including some that cater to Unicode code
points, which are characters that require two (or sometimes four) bytes to express
them.
The original variable-length strings defined by Turbo Pascal are limited to 255
characters, and are called short strings. Short strings have a logical length that varies
depending on what you put into the string. Strings of different logical lengths may
be assigned to one another as long as the real, physical lengths of the strings are not
exceeded.
A short string has two lengths: a physical length and a logical length. The physical
length is the amount of static memory that the string actually occupies. This length is
set at compile time and never changes. The logical length is the number of characters
currently considered to be stored in the string. This can change as you work with
the string. The logical length (which from now on we will simply call the length) is a
numeric value stored as part of the string itself and can be read by your code.
A string variable is defined using the reserved word STRING. The default
physical length for a string defined as STRING in FreePascal is 255 characters.
202 FreePascal from Square One, Volume 1
(This differs from Turbo Pascal V3.0 and earlier, in which the STRING type had
no default length, and derived string types of some given size had to be explicitly
declared in the TYPE section of your programs.) You may not always need a string
that physically large (for example, a telephone number fits comfortably in less than
20 characters) and you may define smaller string types to save memory by placing
the physical size after the reserved word STRING in brackets:
VAR
Message : String[80]; { Physical size = 80 }
Name : String[30]; { Physical size = 30 }
Address : String[30]; { Physical size = 30 }
State : String[2]; { Physical size = 2 }
TYPE
String80 = String[80];
String30 = String[30];
Buffer = String[255];
Once you have defined these types, declare all string variables that are to have
a physical length of 30 characters as type String30. This way, all such strings will
have identical types and not simply compatible types.
So what is a short string, physically? A string is an array of characters indexed
from 0 to the physical length. Character 0 is special, however: It is the length byte and
it holds the logical length of the string at any given time. The length byte is set by the
runtime code when you perform an operation on a string that changes its logical
length. Assignment and string concatenation using the “+” operator are two ways
to do this. The Concat function is another that I’ll discuss later.
Strings may be accessed as though they were in fact arrays of characters. You can
reference any character in the string, including the length byte, with a normal array
reference:
VAR
MyString : String[15];
Derived Types and Data Structures 203
CharS : Integer;
OUTChar : Char;
MyString := ‘Galadriel’;
CharS := ORD(MyString[0]); { CharS now equals 9 }
OUTChar := MyString[6]; { OUTChar now holds ‘r’ }
Even though the runtime code treats the length byte as a number, it is still an
element in an array of Char and thus cannot be assigned directly to type Byte or
Integer. To assign the length byte to a numeric variable, you must use the ORD
transfer function, which is Pascal’s orderly way of transferring a character value to a
numeric value. (See Section 11.5.)
Having told you that this is possible, let me warn you: Don’t do it. It was common
enough in the Turbo Pascal era, because short strings were more or less all there
were in the string department. String handling in modern Pascal compilers has
gradually moved away from short strings toward (much) longer strings allocated
dynamically, as dynamic arrays are. I’ll explain dynamic strings in the next section.
The warning is that dynamic strings don’t have a length counter at element 0, so
accessing element 0 won’t accomplish anything useful, and (depending on how the
compiler is configured) may generate an error.
No. Use the predefined string-handling functions and procedures built into
FreePascal instead. The Length function is a good example, and makes directly
accessing a short string’s length counter unnecessary. It returns the current logical
length of a string variable of any type:
CharCount := Length(MyString); { CharCount now equals 9 }
I’ll discuss Length, Concat, and all the other built-in string handling functions and
procedures in detail in Chapter 12.
Characters and short strings are compatible in some limited ways. You can assign
a character value (stored either in constant form or as a Char variable) to a string
variable. The string variable then has a logical length of one:
VAR
OUTChar : Char;
The reverese, however, isn’t true: A string (even one having a length of zero or
one) cannot be assigned to a variable of type Char.
You can compare a string variable to a character literal:
204 FreePascal from Square One, Volume 1
In any string comparison, the two strings must be identical in both length and
content for a TRUE value to be generated by the expression.
It’s possible to assign a string to another string with a shorter physical length. This
will cause neither a compile time nor a run time error. What it will do is truncate the
data from the larger string to the maximum physical length of the smaller string.
You can concatenate a character literal or variable to a string variable, using
either the Concat function or (more commonly) the “+” operator. Concat (which I’ll
describe briefly in section 12.2) is a holdover from ancient times, and I recommend
using the “+” operator instead. Be aware of it if you have to deal with older Pascal
code. For new code, use “+”.
ANSI Strings
Short strings were first developed for UCSD Pascal in 1978, and have been
supported by nearly all Pascal compilers since then. As I explained earlier, short
strings may be at most only 255 characters long. That’s useful, but still limiting.
FreePascal’s ANSIString type (introduced with Delphi 2.0) eliminates any
practical length limitations. ANSIString variables can be as long as 4,294,967,295
characters, which is plenty, and in fact represent more memory than a lot of low-
end computers actually provide. You can load entire text documents into a single
ANSIString variable for word counting or other document-wide operations.
Four gigabytes is a lot of room; this entire book will fit in a single ANSIString
variable fifty times over.
Internally, however, the two string types are vastly different. An ANSIString
variable is a slightly enhanced dynamic array of characters. It’s stored in dynamic
memory on the heap, just as dynamic arrays are. An ANSIString variable is really
a pointer to the string data on the heap. Ahead of the string data is a 32-bit length
counter, plus a reference counter. (More on this at the end of this section.) Also,
every ANSIString is guaranteed to be terminated by a null character. Any string
operation that changes the number of characters in the variable will move the null
appropriately. Null termination is there primarily to make ANSIString variables
compatible with PChar strings (see below) which are generally used when Pascal
code needs to interface with code from other languages that use PChar as their
primary string type, like C and C++.
You can use all of the standard string functions with ANSIString, including
Length. In fact, if you stick to standard functions (rather than attempting to inspect
and manipulate the data on the heap by other means) and respect the 255-character
limitation on short strings, there is no significant difference in how the two string
Derived Types and Data Structures 205
VAR
GreatBigString : STRING; {Will compile as ANSIString }
{$H-}
AncientString : STRING; {Will compile as ShortString }
{$H+}
AnotherBigString : STRING; {Will compile as ANSIString }
There’s one exception to this rule: When you include a length value in the string
variable definition, FreePascal will treat the variable as a short string, irrespective of
the current state of the $H directive. The string definition below will always compile
StreetName as ShortString:
VAR
StreetName : STRING[80]; {Will always compile as ShortString }
Of course, if you turn $H to minus, make sure you turn it back to plus, or all
STRING definitions from then on will be compiled as ShortString.
ANSIString references
An ANSIString variable is a dynamic variable, existing entirely on the heap. The
variable is in fact a pointer to the string’s data, preceded by two 32-bit values: a
length counter, and a reference counter. The reference counter is part of FreePascal’s
machinery supporting something called lifetime management. Lifetime management
applies to several data types that reside on the heap, especially dynamic string types
(there are several) and dynamic arrays.The idea is that data blocks on the heap that
are no longer being pointed to (referenced) by a pointer of some sort should not
be allowed to take up memory forever. If you’ve never dealt with Pascal pointers
before, this section may be difficult to follow. I’ll deal with them at length in a future
book; do the best you can here, or come back after learning more about the heap.
206 FreePascal from Square One, Volume 1
VAR
GreenString, RedString : ANSIString;
. . .
RedString := GreenString;
{ RedString now points to GreenString’s data. Ref counter = 2 }
RedString := ‘Red’;
{ RedString now has its own heap data block. GreenString’s }
{ ref counter is now back to 1. RedString’s ref counter is 1. }
GreenString := ‘’;
{ GreenString is now empty. Ref count is 0. The “garbage }
{ collection” code releases GreenString’s data block. Now }
{ GreenString has no heap data and is again a null pointer. }
The key point is that when an ANSIString becomes empty, the data block it once
had on the heap becomes useless, and its memory wasted. Lifetime management
uses its “garbage collector” function to de-allocate that memory so that the memory
may be used by other dynamic variables in the program.
If this sounds scary, relax. It’s all handled automatically by the runtime code, and
(especially while you’re still a beginner) you don’t need to be aware of it. Once you
learn about Pascal pointers, I guarantee that it will all make a great deal more sense!
strings. The Strings unit contains a number of short procedures that manipulate C-
style strings, and if you wish to use C-style strings you must include the Strings unit
in your USES statement. (More about units and USES in Chapter 15.) FreePascal
includes the Strings unit. The C-style string type is PChar. It’s basically a pointer
to a sequence of characters set out in static memory at compile time. PChar strings
are not stored in dynamic memory on the heap, as are ANSIString strings. Using
various string functions and operators in the Strings unit, characters are stored in
the area of memory allocated to the PChar, and after the last character containing
real data (not after the last byte allocated for the string!) there is a null character,
which is a binary 0. The null character moves up and down the string as characters
are written to the string. There is no length counter and no reference counter. To
determine the length of a PChar string at any given time, you must literally scan the
string, counting characters until you hit the null. The Strings unit has functions that
perform this and many other services.
Using C-style strings requires that you be proficient in the use of pointers, which
is a great deal to ask of beginning Pascal programmers, especially when the use
case for C-style strings is so narrow. I recommend not bothering with them unless
you are forced to deal with Microsoft Windows API calls or other code libraries
that were designed to be called from the C language. You can find more about them
online, and I will not be discussing them further in this book.
“Wide” strings
One other category of string types needs to be mentioned, even though I’m not going
to cover them in detail in this book. (I may add a more detailed description in a future
update.) Those are the “wide” strings, which are strings of Unicode characters.
Unicode is an industry standard laying out a character format for characters
beyond the 255 characters present in the ASCII character set used in English and
Western European software. Each Unicode character is defined in two bytes rather
than one. This allows many other alphabets (such as Cyrillic or Arabic) to be used
in software.
Type UnicodeString is much like ANSIString, except that two adjacent bytes
are used to represent each character, and the string as a whole is terminated with
two null characters.
There is a great deal more to Unicode than this, and for the time being I must
refer you online (for the big picture) and to the FreePascal doc for details.
Standard Pascal only allows simple constants: Integers, characters, reals, Booleans,
and strings. Turbo Pascal introduced typed constants, meaning constants with an
explicit type that are initialized to some specific value. FreePascal continues and
expands that support.
Calling typed constants “constants” is not entirely fair. Real constants are
hardcoded “in-line” into the machine code produced by the compiler, with an
actual physical copy of the constant dropped in everywhere it is named. The
constant thus exists at no single address. Turbo Pascal’s typed constants are
actually static variables that are initialized at runtime to values taken from the
source code. They exist at one single address, which is referenced anytime the
typed constant is used.
Typed constants also violate the most fundamental proscription of constants in
all languages: They may be changed during the course of a program run. Of course,
you are not obligated to alter typed constants at runtime, but the compiler will not
stop you if you try.
With that in mind, it might be better to think of typed constants as a means
of forcing the compiler to initialize complicated data structures. Standard Pascal
has no means of initializing variables automatically. If values are to be placed into
variables, you must place them there somehow, either from assignment statements
or by reading values in from a file. For example, you could initialize an array of
fifteen integers this way:
VAR
Weights : ARRAY[1..15] OF Integer;
Weights[1] := 17;
Weights[2] := 5;
Weights[3] := 91;
Weights[4] := 111;
Weights[5] := 0;
Weights[6] := 44;
Weights[7] := 16;
Weights[8] := 3;
Weights[9] := 472;
Weights[10] := 66;
Weights[11] := 14;
Weights[12] := 38;
Weights[13] := 57;
Weights[14] := 8;
Weights[15] := 10;
For fifteen values this may seem manageable. But suppose you had fifty values?
Derived Types and Data Structures 209
Or a hundred? At that point typed constants become very attractive. This same
array could be initialized as a structured constant like so:
CONST
Weights : ARRAY[1..15] OF Integer =
(17,5,91,111,0,44,16,3,472,66,14,38,57,8,10);
Array constants
The numeric array example above is a simple, one-dimensional array constant.
Its values are placed, in order, between parentheses, with commas separating the
values. You must give one value for each element of the array constant. The compiler will not
allow you to initialize some values of an array and leave the rest “blank.” You must
do all of them or none at all. If the number of values you give does not match the
number of elements in the array, the compiler will display an error message.
If you only need to initialize a few values out of a large array, (leaving the others
undefined) it makes more sense to go back to individual assignment statements.
You may also define multidimensional array constants. The trick here is to enclose
each dimension in parentheses, with commas separating both the dimensions and the
items. A single pair of parentheses must enclose the entire constant. The innermost
nesting level represents the rightmost dimension from the array declaration. An
example will help:
CONST
Grid : ARRAY[0..4,0..3] OF Integer =
((4,6,2,1),
(3,9,8,3),
(1,7,7,5),
(4,1,7,7),
(3,1,3,1));
four columns, and might represent game pieces on a game grid. Adding a third
dimension to the game (and the grid) would be done this way:
CONST
Space : ARRAY[0..7,0..4,0..3] OF Integer =
(((4,6,2,1),(3,9,8,3),(1,7,7,5),(4,1,7,7),(3,1,3,1)),
((1,1,1,1),(1,1,1,1),(1,1,1,1),(1,1,1,1),(1,1,1,1)),
((2,2,2,2),(2,2,2,2),(2,2,2,2),(2,2,2,2),(2,2,2,2)),
((3,3,3,3),(3,3,3,3),(3,3,3,3),(3,3,3,3),(3,3,3,3)),
((4,4,4,4),(4,4,4,4),(4,4,4,4),(4,4,4,4),(4,4,4,4)),
((5,5,5,5),(5,5,5,5),(5,5,5,5),(5,5,5,5),(5,5,5,5)),
((6,6,6,6),(6,6,6,6),(6,6,6,6),(6,6,6,6),(6,6,6,6)),
((7,7,7,7),(7,7,7,7),(7,7,7,7),(7,7,7,7),(7,7,7,7)));
The values given for the two-dimensional array have been retained here to see
how the array has been extended by one dimension. Note that the list of values in
an array constant must begin with the same number of left parentheses as the array
has dimensions. Remember, also, that every element in the array must have a value
in the array constant declaration.
Notice that this mechanism allows you to initialize 160 different integer values
in a relatively small space. Imagine what it would have taken to initialize this array
with a separate assignment statements for each array element!
Record constants
Record constants are handled a little bit differently. You must first declare a record
type and then a constant containing values for each field in the record. The list of
values must include the name of each field, followed by a colon, and then the value
for that field. Items in the list are separated by semicolons. As an example, consider
a record containing configuration values for a serial terminal program:
TYPE
BPS = (B110,B300,B1200,B2400,B4800,B9600);
ParityType = (EvenParity,OddParity,NoParity);
TermCFG = RECORD
LocalAreaCode : String[3];
UseTouchtones : Boolean;
DialOneFirst : Boolean;
BaudRate : BPS;
BitsPerChar : Integer;
Parity : ParityType
END;
Derived Types and Data Structures 211
CONST
Config : TermCFG =
(LocalAreaCode : ‘716’;
UseTouchtones : True;
DialOneFirst : True;
BaudRate : B1200;
BitsPerChar : 7;
Parity : EVEN_PARITY);
The structured constant declaration for Config must come after the type definition
for TermCFG, otherwise the compiler would not know what a TermCFG was.
Note that FreePascal allows multiple CONST sections, and will allow you to place
a CONST section after a TYPE section. This would not be allowed under Standard
Pascal. Also, note that there is no BEGIN/END bracketing in the declaration of
Config. The parentheses serve to set off the list of field values from the rest of your
source code.
Set constants
Declaring a set constant is not very different from assigning a set value to a set
variable:
CONST
Uppercase : SET OF Char = [‘A’..’Z’];
Important: The Chr notation shown above will not work when declaring set
constants. You have two alternatives:
1) Express the character as a control character by placing a caret symbol (^) in
front of the appropriate character. The bell character, Chr(7), would be expressed
as ^G.
2) Express the character as its ordinal value preceded by a pound sign (#). The
bell character would be expressed as #7. This notation is more useful for expressing
characters falling in the “high” 128 bytes of type Char, corresponding to the line-
drawing, mathematical, and foreign language characters on PCs.
For example, the set of whitespace characters is a useful set constant:
CONST
212 FreePascal from Square One, Volume 1
or, alternatively:
CONST
Whitespace : SET OF Char = [^H,^J,^L,^M,’ ‘];
The following three routines show you how to use set constants in simple
character-manipulation tools. If you do a lot of character manipulation and find a
great deal of use for these three set constants, you might also declare them at the
global program level so that any part of the program can use them. Remember that,
declared as it is here locally to the individual functions, the Whitespace constant
cannot be accessed from outside the function! This is a consequence of the scoping
rules of procedures and functions, which I’ll cover in detail in Chapter 13.
CONST
Lowercase : SET OF Char = [‘a’..’z’];
BEGIN
IF Ch IN Lowercase THEN CapsLock := Chr(Ord(Ch)-32)
ELSE CapsLock := Ch
END;
CONST
Uppercase : SET OF Char = [‘A’..’Z’];
BEGIN
IF Ch IN Uppercase THEN DownCase := Chr(Ord(Ch)+32)
ELSE DownCase := Ch
END;
CONST
Whitespace : SET OF Char = [#8,#10,#12,#13,’ ‘];
BEGIN
IsWhite := Ch IN WhiteSpace
END;
Derived Types and Data Structures 213
214 FreePascal from Square One, Volume 1
Chapter 9.
Structuring Code
C ontrolling the flow of program logic is one of the most important facets of any
programming language. Conditional statements that change the direction of
the flow of control, looping statements that repeat some action a number of times,
switch statements that pick one course out of many based on some controlling
value, all make useful programs possible.
Pascal, furthermore, would like you the programmer to direct the flow of control
in a structured, rational manner, so the programs you write are easy to read and
easy to change when they need changing. For this reason, wild-eyed zipping around
inside a program is difficult in Pascal.
The language syntax itself suggests with some force that programs begin at
the top of the page and progress generally downward, exiting at the bottom when
the work is done. Multiple entry and exit points and unconditional branching via
GOTO are more trouble to set up—which is just as well, because they can be a lot
more trouble to understand and debug when they don’t go quite where you want
them to, when you want them to, and therefore do what you want them to.
And making things go where you want them to is the fundamental purpose of
this admittedly large chapter.
215
216 FreePascal from Square One, Volume 1
PROGRAM Rooter;
VAR
R,S : Real;
BEGIN
Writeln(‘>>Square root calculator<<’);
Writeln;
Write(‘>>Enter the number: ‘);
Readln(R);
S := Sqrt(R);
Writeln(‘ The square root of ‘,R:7:7,’ is ‘,S:7:7,’.’)
END.
The statements bracketed by BEGIN and END in the above example are all
simple statements, but that need not be the case. Compound statements may also
be parts of larger compound statements:
PROGRAM BetterRooter;
VAR
R,S : Real;
BEGIN
Writeln(‘>>Better square root calculator<<’);
Writeln;
R:=1;
WHILE R<>0 DO
BEGIN
Writeln(‘>>Enter the number (0 to exit): ‘);
Readln(R);
IF R<>0 THEN
BEGIN
S := Sqrt(R);
Writeln(‘ The square root of ‘,R:7:7,’ is ‘,S:7:7,’.’);
Writeln
END
END;
Writeln(‘>>Square root calculator signing off...’)
END.
This program contains one compound statement nested inside another, and both
nested within a third compound statement that is the body of the program itself.
This is a good place to point out the “prettyprinting” convention that is virtually
always used when writing Pascal code. The rule on prettyprinting turns on
compound statements: Each compound statement is indented two spaces to the
right of the rest of the statement in which it is nested.
Structuring Code 217
It’s also crucial to remember that prettyprinting is ignored by the compiler. It is strictly
a typographical convention to help you sort out nested compound statements by
“eyeballing” rather than counting BEGINs and ENDs. You could as well (as some
do) indent by three or more spaces instead of two. You could also (as some do) not
indent at all. The compiler doesn’t care. But the readability of your program will
suffer if you don’t use prettyprinting.
There is one other thing about compound statements that might seem obvious to
some but very un-obvious to others: Compound statements can be used anywhere a
simple statement can. Anything between a BEGIN/END pair is treated syntactically
by the compiler just as single simple statement would be.
Compound statements may also be bounded by the reserved words REPEAT
and UNTIL, as I’ll show a little later in this chapter.
9.2. IF/THEN/ELSE
A conditional statement is one that directs program flow in one of two directions
based on a Boolean value. In Pascal the conditional statement is the IF/THEN/ELSE
statement. The ELSE clause is optional, but every IF reserved word must have a
THEN reserved word associated with it. In its simplest form, such a statement is
constructed this way:
IF <Boolean expression> THEN <statement>
The way this statement works is almost self-explanatory from the logic of the
English language: If the Boolean expression evaluates to True, then <statement> is
executed. If the Boolean expression evaluates to False, control “falls through” to the
next statement in the program.
Adding an ELSE clause makes the statement look like this:
Here, if the expression evaluates to True then <statement 1> is executed. If the
expression evaluates to False, then <statement 2> is executed. If an ELSE clause
exists, you can be sure that one or the other of the two statements will be executed.
Either or both of the statements associated with IF/THEN/ELSE may be
compound statements. Remember that a compound statement may be used
anywhere a simple statement may. For example:
218 FreePascal from Square One, Volume 1
IF I < 0 THEN
BEGIN
Negative := True; { Set negative flag }
I := Abs(I) { Take abs. value of I }
END { Never semicolon here! }
ELSE Negative := False; { Clear negative flag }
Nested IF Statements
Since an IF statement is itself a perfectly valid statement, it may be one or both of
the statements contained in an IF/THEN/ELSE statement. IFs may be nested as
deeply as you like—but remember that if someone reading your code must dive
too deeply after the bottommost IF, he or she may lose track of things and drown
before coming up again. If the sole purpose of multiply-nested IFs is to choose one
alternative of many, it is far better to use the CASE statement, which will be covered
in Section 9.3. Structurally, such a construction looks like this:
The bottom line here is that all Boolean expressions must evaluate to True before
<statement> is executed.
Such a downward escalator of IFs is often hard to follow. Sharp readers may
already be objecting that this same result could be done with AND operators:
This is entirely equivalent to the earlier nested IF with one sneaky catch: Here, all
the Boolean expressions are evaluated before a decision is reached on whether or
Structuring Code 219
not to execute <statement>. With the nested IF, the compiler will stop testing as
soon as it encounters a Boolean expression that turns up False. In other words, in
a nested IF, if <Boolean3> is found to be False, the compiler never even evaluates
<Boolean4> or <Boolean5>.
Nitpicking? No! There are times when in fact the reason for <Boolean3> might
be to make sure <Boolean4> is not tested in certain cases. Divide by zero is one of
those cases. Consider this:
IF AllOK THEN
IF R > PI THEN
IF S <> 1 THEN
IF (R / ((S*S)-1) < PI) THEN
CalculateRightAscension;
Here, a value of S = 1 will cause a divide-by-zero error if the code attempts to evaluate
the next expression. So the code must stop testing if S <> 1 turns up False or risk
crashing the program with a runtime divide-by-zero error.
With nested IFs you can determine the sequential order in which a series of tests
is done. A string of AND operators between Boolean expressions may evaluate
those expressions in any order dictated by the code generator’s optimization logic.
If one of your tests carries the hazard of a run-time error, use nested IFs.
Nested ELSE/IFs
The previous discussion of nested IFs did not include any ELSE clauses. Nesting IF’s
does not preclude ELSEs, though the use and meaning of the statement changes
radically. Our previous example executed a series of tests to determine whether
or not a single statement was to be executed. By using nested ELSE/IFs you can
determine which of many statements is to be executed:
The code will descend the escalator, and as soon as it finds a Boolean with a
value of True, it will execute the statement associated with that Boolean. The tests
are performed in order, and even if <Boolean4> is True, it will not be executed (or
<Boolean4> even evaluated) if <Boolean2> is found to be True first.
220 FreePascal from Square One, Volume 1
The final ELSE clause is not necessary; it provides a “none of the above” choice,
in case none of the preceding Boolean expressions turned out to be true. You
could simply omit it and control would fall through to the next statement without
executing any of the statements contained in the larger IF statement.
As with nested IFs described above, nested ELSE/IFs allow you to set the order
of the tests performed, so that if one of them carries the danger of a run-time error,
you can defuse the danger with an earlier test for the dangerous values.
The CASE statement is a shorthand form of nested ELSE/IFs in which all of the
Boolean expressions are of this form: <value> = <value> and the type of all <value>s
is identical. We’ll look at the CASE statement in detail in Section 9.3.
IF AllOK AND
(R > PI) AND
(S <> 1) AND
(R / ((S*S)-1) < PI) { Could trigger divide-by-zero! }
THEN CalculateRightAscension;
In most cases, the compiler will evaluate all four of the Boolean subexpressions
before combining them into a single Boolean value as the result of the entire, larger
expression. As we mentioned earlier, if S ever takes on a value of 1, the fourth
subexpression will trigger a divide-by-zero error, since when S equals 1, R is divided
by ((S*S)-1) which equals zero.
By turning on short-circuit Boolean evaluation, the compiler is forced to evaluate
Boolean expressions from left to right, and it will stop evaluation as soon as it is
sure that testing further will not change the ultimate value of the expression.
How can it be sure that further testing won’t change things? Think about the
meaning of the AND operator. If any number of Boolean subexpressions are
ANDed together, a single False value will force the whole expression to False. So once
that first False turns up in evaluating an expression from left to right, the whole
Structuring Code 221
expression will be False no matter what else waits to be evaluated further to the
right.
This technique is called “short circuit,” because it quits when possible before
evaluating an entire expression. The primary reason for it is that it can make your
programs run more quickly if there are many Boolean expressions to be evaluated.
Any time you can perform the same job by executing less code, the job will go more
quickly.
However, the side benefit of allowing you to arrange your Boolean expressions
so that “dangerous” subexpressions are to the right of “sentinel” subexpressions that
guard against the dangerous condition is perhaps more generally useful. Returning
to the example above: If short-circuit Boolean evaluation is enabled, the compiler
will evaluate the subexpression (S <> 1). If S is equal to 1, evaluation stops there, and
the dangerous expression (R / ((S*S)-1) < PI) will never be evaluated, eliminating
the possibility of a divide-by-zero runtime error bringing the program to a halt.
Short-circuit evaluation doesn’t only apply to the AND operator. A string
of expressions ORed together will be evaluated only until the first True value is
encountered. More complicated expressions are simply ground through until the
compiler is certain that nothing further can change the ultimate Boolean value.
Then it will stop.
Using short-circuit Boolean evaluation is made easier by the fact that the compiler
assumes it by default. What is called “complete” Boolean expression evaluation
must be explicitly chosen if you want to use it. Forcing complete Boolean expression
evaluation is done through the /B+ switch when using the command-line version of
the compiler.
You can also force complete Boolean expression evaluation by inserting a {$B+}
compiler command in your source code. By bracketing a region of code between
a {$B+} command and a {$B-} command, you can force complete evaluation only
between the two commands, and use short-circuit evaluation throughout the rest
of your program.
Why would you ever want to use complete Boolean expression evaluation?
Just as you sometimes need to ensure that a certain subexpression will never be
evaluated (as we saw above), you must sometimes ensure that every subexpression
is always evaluated.
Almost all such cases involve Pascal functions that return Boolean values after
doing some sort of necessary work that must be completed regardless of which
value the function returns. (For those of you reading this book serially who may not
yet understand Pascal functions and how they return values, look ahead to Chapter
10, especially Section 10.1.)
222 FreePascal from Square One, Volume 1
IF AllocateBigBuffer(BigBuffPtr) AND
AllocateLittleBuffer(LittleBuffPtr)
THEN LoadBothBuffers ELSE
BEGIN
IF BuffPtr1 <> NIL THEN LoadBuffer1
ELSE IF BuffPtr2 <> NIL THEN LoadBuffer2
ELSE UseDiskSwap := True
END;
This example comes from a program that needs a lot of memory for buffers. It
attempts to allocate both its large and its small buffers if possible. If only the large
buffer can be allocated without leaving enough RAM for the small buffer, so be it.
Or, if the large buffer cannot be allocated but the small buffer can, the small buffer
will be allocated and used. Finally, if the program can’t find enough memory to
allocate either RAM-based buffer, it will use a disk-swapping system to make disk
space serve as (slower) memory space for buffer operations. BuffPtr1 and BuffPtr2
are pointers that are initialized to point to their buffers when those buffers are
allocated. If there isn’t enough memory to allocate a buffer, its pointer is returned
with a value of NIL to indicate that its buffer could not be created.
Whether or not both buffers can be created in memory, both pointers must be
initialized to some value, either a legitimate pointer value to an allocated buffer, or else
NIL. Later on in the program, the logic will need to test those pointers to see if a given
buffer exists. Having either pointer in an uninitialized state could be disastrous.
If we allowed short-circuit Boolean expression evaluation here, function
AllocateLittleBuffer would never be executed if AllocateBigBuffer failed to
find enough memory to allocate the large buffer. LittleBuffPtr would be left in an
uninitialized state, and later on, the program could malfunction if it tried to test the
uninitialized LittleBuffPtr.
In this situation, the entire Boolean expression
IF AllocateBigBuffer(BigBuffPtr) AND
AllocateLittleBuffer(LittleBuffPtr)
9.3. CASE
Choosing between one of several alternative control paths is critical to computer
programming. We’ve seen how IF/THEN/ELSE in its simplest form can choose
between two alternatives based on a Boolean expression. By nesting IF/THEN/
ELSE statements one within another, we can choose among many different control
paths, as we saw in the previous section.
The problem of readability appears when we nest IF statements more than two
or three deep. Nested IF/THEN/ELSE gets awkward and non-intuitive in a great
hurry when more than three levels exist. Consider the problem of flashing a message
on a display screen based on some input code number. A problem reporting system
on a display-equipped car computer system might include a statement sequence
like this:
Beep;
Writeln(‘*****WARNING*****’);
IF ProblemCode = 1 THEN
Writeln(‘[001] Fuel supply has fallen below 10%’)
ELSE IF ProblemCode = 2 THEN
Writeln(‘[002] Oil pressure has fallen below min spec’)
ELSE IF ProblemCode = 3 THEN
Writeln(‘[003] Engine temperature is too high’)
ELSE IF ProblemCode = 4 THEN
Writeln(‘[004] Battery voltage has fallen below min spec’)
ELSE IF ProblemCode = 5 THEN
Writeln(‘[005] Brake fluid level has fallen below min spec’)
ELSE IF ProblemCode = 6 THEN
Writeln(‘[006] Transmission fluid level has fallen below min spec’)
ELSE IF ProblemCode = 7 THEN
Writeln(‘[007] Radiator water level has fallen below min spec’)
ELSE
Writeln(‘[***] Logic failure in problem reporting system’);
This will work well enough, but it takes some picking through to follow it clear
to the bottom. This sort of selection of one statement from many based on a single
selection value is what the CASE statement was created for. Rewriting the above
statement with CASE gives us this:
Beep;
Writeln(‘*****WARNING*****’);
CASE ProblemCode OF
1 : Writeln(‘[001] Fuel supply has fallen below 10%’);
2 : Writeln(‘[002] Oil pressure has fallen below min spec’);
3 : Writeln(‘[003] Engine temperature is too high’);
4 : Writeln(‘[004] Battery voltage has fallen below min spec’);
224 FreePascal from Square One, Volume 1
Here, ProblemCode is called the case selector. The case selector may be an expression
or a variable. It holds the value upon which the choice among statements will be made.
The numbers in a line beneath the word CASE are called case labels. Each case label is
followed by a colon and a statement. The statement may be simple or compound.
When the CASE statement is executed, the case selector is evaluated and its
value is compared, one by one, against each of the case labels. If a case label is found
equal to the value of a case selector, the statement associated with that case label is
executed. Once the statement chosen for execution has completed executing, the
work of the CASE statement is done and control passes to the rest of the program.
Only one (or none, see below) of the several statements is executed for each pass
through the CASE statement.
Although my examples here show only single statements associated with case
labels, compound statements are perfectly legal.
If no case label matches the value of the case selector, the statement following the
ELSE is executed. ELSE is optional, by the way; if no ELSE is found, control falls
through to the next statement in the program.
The general form of a CASE statement is this:
There may be as many case labels as you like, up to 256. You may be puzzling over
the fact that what we pointed out as case labels are called constant lists in the general
form. In our first example, each case label was only a single numeric constant. A
case label may also be a list of constants separated by commas. Remember that the
case label is the list of constants associated with a statement; each statement can
only have one case label. And do not forget that a case label may never be a variable!
For another example, let’s look at some code for a mail-in survey analysis system.
The responses must be grouped by geographical regions of the country. State is an
Structuring Code 225
enumerated type including all the standard two-letter state name abbreviations, in
alphabetical order. This particular code fragment tallies the number of responses
from each geographical region:
TYPE
{II = Indiana; OG = Oregon to avoid reserved word conflict}
State = (AK,AL,AR,AZ,CA,CO,CT,DE,DC,FL,GA,HI,ID,IL,II,
IA,KS,KY,LA,MA,MD,ME,MI,MN,MO,MS,MT,NE,NV,NH,
NJ,NM,NY,NC,ND,OH,OK,OG,PA,RI,SC,SD,TN,TX,UT,
VA,VT,WA,WI,WV,WY)
VAR
FromState : State;
CASE FromState OF
CT,MA,ME,NH,
RI,VT : CountNewEngland := CountNewEngland + 1;
DC,DE,MD,NJ,
NY,PA : CountMidAtlantic := CountMidAtlantic + 1;
FL,GA,NC,SC : CountSoutheast := CountSoutheast + 1;
IA,IL,II,MI,
MN,OH,WI,WV : CountMidwest := CountMidwest + 1;
AL,AR,KY,LA,
MO,MS,TN,VA : CountSouth := CountSouth + 1;
KS,ND,NE,SD,
WY : CountPlains := CountPlains + 1;
AK,CA,CO,HI,
ID,MT,OG,UT,
WA : CountWest := CountWest + 1;
AZ,NM,NV,OK,
TX : CountSouthwest := CountSouthwest + 1;
END; { CASE }
Here you can see that a case label can indeed be a list of constants. Also note that
there is no ELSE clause here because every one of the possible values of type State
is present in one of the case labels.
CASE cautions
The most important thing to remember about case labels is that they must be
constants or lists of constants. A particular value may appear only once in a CASE
statement. In other words, the value IL (from the last example) could not appear
in both the CountMidwest and the CountSouth case labels. The reason for this
should be obvious; if a value is associated with more than one statement, the CASE
logic will not know which statement to execute for that case label value.
You should be careful when using a case selector of type Integer. Case selectors
may only have values between 0 and 255. An integer case selector may have a value
226 FreePascal from Square One, Volume 1
much larger than 255, and when it does the results of executing the CASE statement are
undefined. If you work a lot with numeric codes (and intend to use CASE structures to
interpret those codes) it’s a good idea to define those codes as subranges of Integer:
TYPE
Keypress = 0..255;
Problem = 0..32;
Priority = 0..7;
Any of these named subrange types may act as case selectors. FreePascal and
Delphi also provide type Byte, which is an unsigned 8-bit integer that can only
take values from 0-255. For that reason, Byte makes the perfect case selector for
numeric values.
FOR I := 1 TO 100 DO
BEGIN
J := Sqrt(I);
Writeln(‘The square root of ‘,I,’ is ‘,J)
END;
There are better ways to lay out a square roots table, obviously, but this gets
the feeling of a FOR loop across very well. I is an integer. J is a real number. The
compound statement between the BEGIN/END pair is executed 100 times. The
first time through, I has the value 1. Each time the compound statement is executed,
the value of I is increased by 1. Finally, after the compound statement has been
executed with the value of I as 100, the FOR statement has done its job and control
passes on to the next statement in the program.
The preceding example is only a particular case of a FOR statement. The general
form of a FOR statement is this:
<start value> and <end value> may be expressions. <control variable> is any
ordinal type, including enumerated types. When a FOR statement is executed, the
following things happen: If <start value> and <end value> are expressions, they are
evaluated and tucked away for reference. Then the control variable is assigned <start
value>. Next, <statement> is executed. After <statement> is executed, the successor
value to the value already in the control variable is placed in the control variable.
The control variable is now tested. If it exceeds <end value>, execution of the FOR
statement ceases. Otherwise <statement> is executed.
The loop repeats until the control variable is incremented past <end value.>
Note that the general definition of a FOR statement does not speak of “adding
one to” the control variable. The control variable is incremented by assigning the
successor value of the current value to the control variable. “Adding” is not really
done at all, not even with integers. To obtain the successor value, the statement
evaluates the expression
Succ(<control variable>)
The function Succ is discussed in more detail later on in Section 11.6. If you
recall our enumerated type Spectrum:
228 FreePascal from Square One, Volume 1
TYPE
Spectrum = (Red, Orange, Yellow, Green, Blue,
Indigo, Violet);
the successor value to Orange is Yellow. The successor value to Green is Blue, and
so on.
A variable of type Spectrum makes a perfectly good control variable in a FOR
loop:
LightSpeed := 3.0E05;
FOR Color := Red TO Violet DO
Frequency[Color] := Lightspeed / Wavelength[Color];
So do characters:
If <start value> and <end value> are the same, the loop is executed once. If <start
value> is higher than <end value>, the loop is not executed at all. That is, <statement>
is not executed, and control immediately falls through to the next statement in the
program.
VAR
Foo : Integer;
BEGIN
<statements>;
FOR Limit := Lo TO Hi DO <statement>;
Foo:=Limit;
<statements>
END;
Structuring Code 229
FreePascal will respond here with an error message. Standard Pascal requires that
control variables be local and non-formal. FreePascal is somewhat more lenient, and
allows control variables to be non-local (this is, not declared in the current block;
see Chapter X for more on this) and also allows formal parameters to be control
variables as long as they are passed by value, that is, if the procedure is given its own
copy of the parameter to play with.
As with most of Pascal’s rules and restrictions, this one was designed to keep
you out of certain kinds of trouble. Understanding what kind of trouble requires a
little further poking at the notion of control variables in FOR loops: To make fault
procedure Runnerup shown above work, some sort of local control variable would
have to be declared in the data declaration section of the procedure, like this:
BEGIN
<statements>;
FOR I := Lo TO Hi DO <statement>;
Foo := I;
<statements>
END;
Now the FOR loop will compile correctly. But there is still something wrong
with this procedure. What it’s trying to do is make use of the control variable
immediately after the loop has executed by assigning its value to Foo. This is also
illegal. Immediately after a FOR statement, the value of the control variable becomes undefined.
This is not a problem, since the end value is accessible in Hi. To make use of the final
value of the control variable, assign Hi to Foo instead of I. The end result will be the
same:
VAR
Foo,I : Integer;
BEGIN
<statements>;
FOR I := Lo TO Hi DO <statement>;
Foo := Hi;
<statements>
END;
230 FreePascal from Square One, Volume 1
There is a good reason for the control variable becoming undefined after its loop
has run its course. After each pass through <statement> the successor value to the
current value in the control variable is computed. In the process, that successor
value may in fact become undefined if the ordinal type being used runs out of values.
Going back to our type Spectrum, what is the successor value to Violet? There is
none; Succ(Violet) is undefined. Consider:
FOR Color := Red TO Violet DO <statement>;
After the loop runs through its seven iterations, Color would hold an undefined
value. If you were allowed to “pick up” the control variable’s value after the run
of a FOR loop, you might in fact be picking up an undefined, nonsense value and
have no way of knowing that it were so. This is why the Standard Pascal definition
declares that control variables are always undefined after a FOR loop to remove
the temptation to “save” the final value of a control variable for later use.
In Borland Pascal V7, a Break statement was added to the language. Break
interrupts execution of a FOR, WHILE, or REPEAT loop before the loop has run
through all the iterations called for. With Break now an option, you cannot always
be sure that the <end value> will match the control variable’s value at the end of the
loop, so you must use a separate variable if you want to keep track of the value in the
control variable and use it after the loop exits.
Within the FOR loop (that is, within <statement>) the control variable must
be treated as a “read-only” value. You can change the value of the control variable
within the FOR loop, but you should do so with extreme hesitation. The REPEAT/
UNTIL and WHILE/DO statements are designed to support this kind of “moving
target” loop, which will execute as often as required to make a control variable
equal to some final value. FOR loops, by contrast, were designed to execute a fixed
number of times, and while executing, the value of the control variable should be
solely under the control of the FOR loop itself.
Now (finally!) you may understand why Pascal forbids using VAR parameters
as control variables. Pascal reserves the right to force a control variable into an
undefined state after its loop is done. Using a VAR parameter as a control variable
might “reach up” out of the procedure and force the VAR parameter into some
unexpected and possibly undefined state. Allowing a procedure to “undefine” a
parameter passed to it is asking for trouble, since it may not be obvious to the calling
logic that its parameter may come back undefined.
Preserve sanity in your programs. Keep your FOR loop control variables local.
Structuring Code 231
When using DOWNTO, keep in mind that if <start value> is lower than <end
value>, <statement> will not be executed at all. This is the reverse of the case for
FOR/TO loops.
VAR
pH : Real;
This snippet of code is a part of a control system for some sort of chemical
processing apparatus. All it must do is fill a tank with water, ensuring that the pH
of the water is at least 7.2. If the water from the water supply comes in as too acidic
(water quality varies widely in some parts of the country) its pH must be brought up
to 7.2 before the water is considered useable.
First the tank is filled. Then the initial pH reading is taken. The water may in fact
be useable from the start, in which case the loop is never executed. But if the water
comes up acidic, the loop is executed. A small quantity of an alkali is added, the
Structuring Code 233
tank is stirred for a while, and then the pH is taken again. If the pH has risen to 7.2,
the loop terminates. If the pH remains too low, the loop is executed again, more
alkali is added, and the pH is tested once more. This will continue until the pH test
returns a value in the variable pH that is higher than 7.2.
It’s crucial to note that an initial pH test was performed. If the variable pH were
not used before, its value is undefined, and testing it for True or False will not have
any real-world meaning. Make sure the Boolean expression is defined before the WHILE/
DO loop is executed! Every variable in the expression must be initialized somehow
before the expression can be “trusted.”
The most important property of a WHILE/DO loop is that its Boolean expression
is tested before <statement> is executed. A corollary to this is that there are cases
when <statement> will never be executed at all. Keep this in mind while we discuss
REPEAT/UNTIL next.
VAR
Drops : Integer;
Complete : Boolean;
Drops := 0;
REPEAT
AddADrop; { Opens valve for 1 drop }
Drops := Drops + 1; { Increment counter }
Signal(Complete) { Read the reaction sensor }
UNTIL Complete;
Note that the drops counter is initialized before the loop begins. A drop is added
to the test vessel, and the drop counter is incremented by one. Then the reaction
sensor is read. If it senses that the reaction has gone to completion, the Boolean
variable Complete is set to True.
Since it takes it least one drop to complete the reaction, (without any drops
of the chemical, the reaction can’t even begin) this series of events must be done
at least once. If the first drop completes the reaction, the loop is performed only
once. Most likely, the loop will have to execute many times before the chemical
reaction goes to completion and Complete becomes True. When this happens,
Drops will contain the number of drops required to complete the reaction. The
value of Drops might then be displayed on a display of some kind attached to the
chemical apparatus.
One interesting thing about REPEAT/UNTIL is that the two keywords do
double duty if <statement> is compound. Instead of bracketing the component
statements between BEGIN and END, REPEAT and UNTIL perform the
bracketing function themselves.
WHILE/DO or REPEAT/UNTIL?
These two types of loops are very similar, and it is sensible to ask why both are
necessary. Actually, WHILE/DO can accomplish anything REPEAT/UNTIL can,
with a little extra effort and occasional rearranging of the order of the statements
contained in the loop. The titration code could be written this way:
Drops := 0;
Complete := False;
WHILE NOT Complete DO
BEGIN
AddADrop;
Drops := Drops +1;
Signal(Complete)
END;
Structuring Code 235
This method requires that Complete be set to False initially to ensure that it
will be defined when the loop first tests it. If Complete were left undefined and it
happened to contain garbage data that could be interpreted as the value True, (if bit
0 is binary 1, for example) the loop might never be executed, and the code would
report that the titration had been accomplished with zero drops of reagent—which
is chemically impossible.
Using REPEAT/UNTIL would prevent that sort of error in logic. Quite simply:
Use REPEAT/UNTIL in those cases where the loop must be executed at least once. Whenever
you write code with loops, always consider what might happen if the loop were
never executed at all—and if anything unsavory might come of it, make sure the
loop is coded as REPEAT/UNTIL rather than WHILE/DO.
and wham! you’re at the location marked by label 150. This straightforwardness leads
inexperienced programmers (especially those first schooled in BASIC) to use them
to get out of any programming spot that they do not fully understand how to deal
with in a Pascal-style structured manner.
I will not caution you, as some people do, never to use a GOTO no matter what.
What I will tell you to do is never use a GOTO when something else will get the job
done as well or better.
Labels
In order to use GOTO, GOTO must have somewhere to go, and in Pascal that
somewhere must be marked by something called a label. Labels, like most everything
else in Pascal, must be predeclared before they are used. Declaring labels is done
in the label declaration section of a program, immediately following the reserved
word LABEL:
236 FreePascal from Square One, Volume 1
LABEL
100,150,200,250,300;
This example LABEL definition statement conforms to ISO Standard Pascal. The
labels themselves must be numeric for Standard Pascal, in the range 0-9999. A label
may mark only one point in a program. That is, you may not mark two different
locations in a single program with the same label.
Most modern Pascal implementations, including FreePascal, extend the label
syntax and allows labels to be ordinary identifiers just like those you use for constants
and variables. You can mix numeric and identifier labels in the same label definition
statement:
LABEL
100,ShutDown,PowerGlitch,200,300,HardwareFailure;
When you mark a statement with a label, you must put a colon after the label.
Look to the code a few paragraphs down for an example.
Numeric and identifier labels are used identically in GOTO statements:
GOTO ShutDown;
GOTO 200;
The effect is the same in either case: Execution continues at the statement in the
program marked by the label.
you could not, from some other part of the block, GOTO label 250. However,
assuming that this snippet of code is not part of some larger structured statement,
you could in fact GOTO label 300 from some other part of the current block.
Whether or not that would perform any useful function is a good question.
This example is very bad practice, but we include it here only to indicate that
you cannot branch into the middle of a WHILE/DO statement from somewhere
outside the statement. Label 250 is accessible only from somewhere between the
BEGIN and END pair.
In general, GOTOs are used to get out of somewhere, not to get in. Standard
Pascal and versions of Borland’s Pascal compilers prior to V7.0 lack two general
looping features that most Pascals now have: BREAK and CONTINUE. BREAK
leaves the middle of a loop and sends control to the first statement after the loop.
CONTINUE stops executing the loop and begins the loop at the top, with the next
value of the control variable.
Without BREAK and CONTINUE, GOTO provides the only reasonably clean
way to get out from inside certain very complicated loops in which a lot is going on
and more than one condition value affects exiting from the loop. Logically speaking,
you can always get out of a loop safely by using the facilities provided by the loop
(exiting at the top with WHILE/DO and at the bottom with REPEAT/UNTIL) but
there are cases when to do so involves tortuous combinations of IF/THEN/ELSE
that might in fact be harder to read than simply jumping out with GOTO. But for
clarity’s sake, always GOTO the statement immediately following the loop you’re
exiting. (This is precisely what the BREAK statement does.)
BREAK and CONTINUE are now in the Pascal language definition, as I’ll
describe below in more detail. That being the case, the possible situations requiring
GOTO have dwindled down to practically nothing.
Such situations are rare enough that FreePascal forbids the use of GOTO by
default. In order to use GOTO, you’ll have to place the {$GOTO ON} compiler
switch in your source code before you use a GOTO statement. The {GOTO OFF}
switch turns off GOTO permissions, and is assumed when you run the compiler
until a {$GOTO ON} is encountered.
With BREAK and CONTINUE in hand, what remains is the very rare need to get
somewhere else right now, especially when the code you need to get to is code to handle
some impending failure or emergency situation that requires immediate attention to
accomplish an orderly shutdown of the equipment, or something of similar seriousness.
This would tend to come up in embedded-system type software that has direct control
over some rather complicated hardware. In thirty-five years of working with Pascal I
have never had to do this. I suspect that if you ever do, you’ll know it.
238 FreePascal from Square One, Volume 1
Total := 0;
TotalAveraged := 0;
FOR I := 1 TO DataTally DO
BEGIN
IF (Total+DataPoints[I]) >= MaxLongInt THEN Break;
Total := Total + DataPoints[I];
Inc(TotalAveraged);
END;
WriteDataToFile;
Here, a FOR loop is totalling data values stored in an array, for averaging. A
statement has been added to prevent the total from overflowing a long integer
variable. If the total plus the next data value is greater than MaxLongInt (a built-
in constant containing the largest legal LongInt value) the BREAK statement is
executed. BREAK takes execution to the first statement after the end of the FOR
loop’s compound statement; in this example, to the WriteDataFile procedure call.
It’s an “early exit” from the FOR loop, and that’s about all to be said for it.
One caution: Don’t count on the value of the FOR loop’s control variable being
available and meaningful once you leave the loop via BREAK. In my example, I’m
keeping a separate count of the number of items successfully tallied, in the variable
TotalAveraged. That’s always a good idea, even if it seems to complicate the logic.
The use of BREAK within CASE is simple to describe: BREAK ends execution
of the statements of an individual case before that case finishes. Remember that a
statement associated with a case label may be a compound statement containing
several simple statements. Sometimes you may want to just call a case done and exit the
CASE statement as a whole. BREAK will do that, and in some cases may be simpler to
understand than wrapping statements inside multiple IF/THEN/ELSE constructs.
CONTINUE is a little subtler. It’s a “restart” procedure that allows you to “short
circuit” the remaining logic in a loop, and start again at the first statement inside
the loop. If the loop is a FOR loop, the control variable is bumped to its next value
before the loop is restarted. If the loop is a WHILE/DO or REPEAT/UNTIL loop,
the loop is restarted at the top, but no variables are affected in any way.
Structuring Code 239
The following example isn’t the best possible coding practice, but it’s simple and
shows what the CONTINUE statement actually does. The example reads a line
from a text file and tests to see if there’s anything in the line. That is, is tests to see
if the length of the line read from the file is 0. If the line contains data and has a
nonzero length, the AddLineToList procedure adds the line to a linked list.
On the other hand, if the length of the line read from the text file is zero, there’s
really no more work to be done with that line anyway. Restart the loop from the
top, which reads the next line from the file. Yes, again: This is an unnecessary
complication, but it illustrates what CONTINUE does.
The real situations where CONTINUE is likely to be useful are complex loops
that do a lot of things and test a lot of different conditions. Such loops make poor
examples in tutorial books like this because their complexity confuses what they’re
really trying to demonstrate.
There can be multiple BREAK or CONTINUE statements inside the same loop. Be
careful, however, that you don’t find yourself using the two statements carelessly, as an
excuse not to think through a loop’s logic. BREAK and CONTINUE are used more
often than GOTO—but not so often that you’ll have them in every loop you write.
PROGRAM Squares;
VAR
I,J : Integer;
BEGIN
Writeln(‘Number Its square’);
FOR I := 1 TO 10 DO
BEGIN
J := I * I;
Writeln(‘ ‘,I:2,’ ‘,J:3)
END;
Writeln;
Writeln(‘Processing completed!’)
END.
Although the second listing appears to exist in four lines, this is only for the
convenience of the printed page; the intent was to express the program as one
continuous line without any line breaks at all.
The second listing above is the compiler’s eye view of your program source code.
You must remember that although you see your program listing “from a height”
as it were, the compiler scans it one character at a time, beginning with the ‘P’ in
Structuring Code 241
PROGRAM and reading through to the “.” after END. All unnecessary “whitespace”
characters (spaces, tabs, carriage returns, linefeeds) have been removed as the
compiler would remove them. Whitespace serves only to delineate the beginnings
and endings of reserved words and identifiers, and as far as the compiler is concerned,
one whitespace character of any kind is as good as one of any other kind. Once the
compiler “grabs” a word or identifier, literal or operator, it tosses out any following
whitespace until it finds a non-whitespace character indicating that a program
element is beginning again.
There are two statements here, framed between BEGIN and END. Smart as the
FreePascal compiler may seem to you, it has no way to know where statements start
and end unless you tell it somehow. If the ‘;’ between I and Writeln were not there,
the compiler would not know for sure if the statement that it sees (so far!) as J:=I*I
ends there or must somehow continue on with Writeln.
Note that there is no semicolon after the second statement. There doesn’t have
to be; the compiler has scanned a BEGIN word and knows that an END should
be coming up eventually. The END word tells the compiler unambiguously that
the previous statement is over and done with. BEGIN and END are not statements.
They are reserved words, acting as delimiters, and only serve to tell the compiler
that the group of statements between them is a compound statement.
It’s useful to think of a long line of statements as a line of boxcars on a rail siding.
Separating each car from the next is a pair of linked couplers. Anywhere two couplers
connect is where, (if boxcars were program statements) you would need a semicolon.
You don’t need one at the front of the first car, or at the end of the last car because the
last car doesn’t need to be separated from anything; behind it is just empty air.
BEGIN
J := J + 5;
IF J > 100 THEN PageEject;
DoPage; { ; legal but not needed here }
END
242 FreePascal from Square One, Volume 1
BEGIN
J := J + 5;
IF J > 100 THEN PageEject;
DoPage;
{ Null statement here! }
END
There is a semicolon between DoPage and the null statement, but none between
the null statement and the END word.
The null statement is in truth a theoretical abstraction. It doesn’t really exist the
same way an IF statement exists. It does no work and generates no code, not even a
NOP (No-Op) assembly language instruction. It serves very little purpose other than to
make certain conditional statements a little more intuitive and readable. For example:
IF TapeIsMounted THEN { NULL } ELSE RequestMount;
but I suspect it’s a matter of taste. Note my convention (which I’ve seen elsewhere)
of inserting the comment { NULL } wherever you use a null statement. It’s like the
bandages around the Invisible Man; they make the guy easier to see and thus keep
him out of trouble.
Another use of the null statement is in CASE statements in which nothing needs
to be done for a specific selector value:
CASE Color OF
Red : { NULL }; { No filter needed }
Orange : InsertFilter(1); { Density 1 }
Yellow : InsertFilter(5); { Density 5 }
Green : InsertFilter(11); { Density 11 }
ELSE InsertFilter(99) { Opaque (99) }
END; { CASE }
In some sort of optical apparatus there is a mechanism for rotating a filter in front
of an optical path. The density of the filter depends on the color of light being used.
No filter is needed for red, and for blue, indigo, or violet the test will not function and
an opaque barrier is moved into the optical path instead of a filter. A null statement
is used for the Red case label.
Structuring Code 243
S ome people think that conditional and looping statements like those we studied
in the previous chapter are the touchstone of structured programming. Not so:
At the bottom of it all, structured programming is the artful hiding of details. The human
mind’s ability to grasp complexity breaks down quickly unless some structure or
pattern can be found in the complexity. I recall (with some embarrassment) writing
a 600-line APL program in 1979, and by the time I wrote the last of it (this being
done over a six week period) I no longer remembered how the first part worked.
The entire program was a mass of unstructured, undifferentiated detail. Those who
have dabbled in APL may lay a little of the blame on APL; most of it I lay on myself.
How does one hide details in computer programs? By identifying sequences of
code that do discrete tasks, and setting each sequence off somewhere, replacing it by
a single word describing (or at least hinting at) the task it does. Such code sequences
are properly called “subprograms.”
245
246 FreePascal from Square One, Volume 1
Functions
A function, by contrast, is not a complete statement. It is more like an expression,
which returns a value that must be used somehow:
VAR
Space,Radius : Real;
Radius := 4.66;
Space := Area(Radius);
The Area function calculates the area for the value Radius, which is passed to it as
a parameter. After it calculates the area, the area value is taken on by the identifier
Area, as though Area were a variable.
Functions in Pascal may return values of just about any predefined or custom
type. This is a huge difference from earlier Pascal compilers, most of which could
return only simple types and strings.
Using the Area function hides the details of calculating areas. There aren’t many
details involved in calculating areas, but for other calculations (matrix inversion
comes to mind) a function can hide thirty or forty lines of complicated code, or
(often) a lot more. So when you’re reading the program and come to a function
invocation, you can think, “Ah, here’s where we invert the matrix” without being
concerned about how the matrix is actually inverted. At that level in reading the
program, the how is not important, so those details are best kept out of sight.
FileActionStatus := CloseTheFile;
BEGIN BEGIN
Writeln(‘Hi there!’) Writeln(‘Hi there!’)
END. END;
The only essential differences between a program and a procedure are the reserved
word PROGRAM and the punctuation after the final END.
Functions are a little different. A function has a type and takes on a value that it
returns to the program logic that invokes it:
248 FreePascal from Square One, Volume 1
CONST
PI = 3.14159;
BEGIN
Area := PI * R * R;
END;
The type of function Area is Real. Inside the function, an expression computing
area for the given radius R is evaluated, and the value is assigned to the function’s
name. Aside from these two distinctions, functions are identical to procedures and
are also miniature programs.
{--------------------------------------------------------------}
{ BoxTest }
{ }
{ Character box-draw demo program; demos concept of procedures }
{ }
{ by Jeff Duntemann }
{ FreePascal 3.0 }
{ Last update 10/29/2016 }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
PROGRAM BoxTest;
USES Crt;
Procedures and Functions 249
TYPE
LineRec = RECORD
ULCorner,
URCorner,
LLCorner,
LRCorner,
HBar,
VBar,
LineCross,
TDown,
TUp,
TRight,
TLeft : String[4]
END;
CONST
PCLineChars : LineRec =
(ULCorner : #201;
URCorner : #187;
LLCorner : #200;
LRCorner : #188;
HBar : #205;
VBar : #186;
LineCross: #206;
TDown : #203;
TUp : #202;
TRight : #185;
TLeft : #204);
VAR
X,Y : Integer;
Width,Height : Integer;
VAR
I : Integer;
BEGIN
IF X < 0 THEN X := (80-Width) DIV 2; { Negative X centers box }
WITH LineChars DO
BEGIN { Draw top line }
GotoXY(X,Y); Write(ULCorner);
FOR I := 3 TO Width DO Write(HBar);
Write(URCorner);
{ Draw bottom line }
250 FreePascal from Square One, Volume 1
GotoXY(X,(Y+Height)-1); Write(LLCorner);
FOR I := 3 TO Width DO Write(HBar);
Write(LRCorner);
{ Draw sides }
FOR I := 1 TO Height-2 DO
BEGIN
GotoXY(X,Y+I); Write(VBar);
GotoXY((X+Width)-1,Y+I); Write(VBar)
END
END
END;
BEGIN
Randomize; { Seed the pseudorandom number generator }
ClrScr; { Clear the entire text window }
WHILE NOT KeyPressed DO { Draw boxes until any key is pressed }
BEGIN
X := Random(72); { Get a Random X/Y for UL Corner of box }
Y := Random(21);
REPEAT Width := Random(80-72) UNTIL Width > 1; { Get Random Height &}
REPEAT Height := Random(25-Y) UNTIL Height > 1; { Width to fit on the }
MakeBox(X,Y,Width,Height,PCLineChars); { display & draw it! }
Delay(25);
END
END.
parameters. The compiler understands the parameter types from reading the
procedure declaration. Types are not given in the invocation:
MakeBox(25,BoxNum+2,30,3,PCLineChars);
fear any side effects outside of the procedure. The copy of the actual parameter it
gets is a truly private copy, strictly local to the procedure itself.
If a variable is passed to a procedure by value the type of the variable must be
compatible with the type of the formal parameter.
VAR
String1 : String80;
String2 : String30;
In this example, strict type checking would prohibit passing either String1 or
String2 as a parameter to procedure DoSomething.
However, FreePascal supports relaxed type checking, which would allow a
ShortString of any length to be passed in the WorkString parameter. Relaxed
type checking is the default. Strict vs. relaxed type checking is controlled with the
{$V} compiler command. The long form is {$VARSTRINGCHECKS}. You must
explicitly use the {$V+} command to impose strict type checking if desired. To use
relaxed type checking and allow strings of any physical length to be passed as VAR
parameters regardless of the formal VAR parameter’s physical length, use {$V-}.
Procedures and Functions 253
The draconian nature of strict type checking for VAR parameters makes a little
more sense when you realize that the variable itself is not copied into the formal
parameter (as with parameters passed by value). What is passed is actually a pointer
to the variable itself. Data is not being moved from one variable to another. Data is
being read from one variable and written back into the same variable. To protect
other data items that may exist to either side of the variable passed by reference, the
compiler insists on a perfect match between formal and actual parameters.
There is a cost to relaxed type checking: A string actual parameter that is too
long to fit into its VAR formal parameter will be truncated to the length of the VAR
formal parameter. No warning will appear, and doing so will lose any character data
beyond the length of the formal parameter.
The {$V} compiler switch is provided mostly as for compatibility with older
versions of Turbo Pascal. Borland Pascal V7 provided a new feature, open string
parameters, that does the same thing a lot more safely. FreePascal supports open
string parameters as well. I’ll discuss them a little later in this chapter.
To illustrate parameter passing, let’s look at a more sophisticated procedure
than we’ve seen so far. The MakeBox procedure I described earlier had several
parameters, all passed by value. For an example of a parameter passed by reference,
consider the shell sort procedure below. Note that this is a procedure, not a complete
program. I’ll present a complete sort demonstration program that incorporates this
procedure a little later in this chapter.
{->>>>ShellSort<<<<--------------------------------------------}
{ }
{ Filename : SHELSORT.SRC -- Last Modified 10/30/2016 }
{ }
{ This is your textbook Shell sort on an array of key records, }
{ defined as the type shown below: }
{ }
{ KeyRec = RECORD }
{ Ref : Integer; }
{ KeyData : String30 }
{ END; }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
VAR
I,J,K : Integer;
Spread : Integer;
254 FreePascal from Square One, Volume 1
VAR
T : KeyRec;
BEGIN
T := RR;
RR := SS;
SS := T
END;
BEGIN
Spread := Recs DIV 2; { First Spread is half record count }
WHILE Spread > 0 DO { Do until Spread goes to zero: }
BEGIN
FOR I := Spread + 1 TO Recs DO
BEGIN
J := I - Spread;
WHILE J > 0 DO
BEGIN { Test & swap across the array }
K := J + Spread;
IF SortBuf[J].KeyData <= SortBuf[K].KeyData THEN J := 0 ELSE
KeySwap(SortBuf[J],SortBuf[K]);
J := J - Spread
END
END;
Spread := Spread DIV 2 { Halve Spread for next pass }
END
END;
This procedure sorts an array of sort keys. A sort key is a record type that consists of
a piece of data and a pointer to a file entry from which the data came. The fastest and
safest way to sort a file is not to sort the file at all, but to build an array of sort keys from
information in the file and sort the array of sort keys instead. The array can then be
written out to a file. Since the data in the array is in sorted (usually alphabetical) order,
it can be searched using a fast binary search function. Once a match to a desired string
is found (in the Key field of a KeyRec record) the RecNum field contains the physical
record number of the record in the file where the rest of the information is stored.
I should point out here that although this was the traditional way to construct
simple databases in Pascal, modern compilers like FreePascal and environments
like Lazarus provide direct access to real database engines like MySQL and SQLite,
so constructing your own databases and sorting key files is no longer necessary for
writing useful data handling software.
Procedures and Functions 255
language theory and other Pascal compilers will recognize this feature as what
Niklaus Wirth calls conformant arrays.
In the header of your procedure or function, a formal array parameter is declared
with the type of the elements in the array, but without any declared bounds. We
might declare a new version of the ShellSort procedure presented earlier in this
chapter this way:
PROCEDURE ShellSort(VAR SortBuf : ARRAY of KeyRec; Recs : Integer);
The new SortBuf parameter is an open array parameter. We are told that it is an
array of KeyRec, but not how large an array SortBuf is. FreePascal is flexible enough
to be able to handle the meshing of the formal array parameter SortBuf with an
actual array of KeyRec at runtime. Either of the array variables below can be passed
to the new ShellSort procedure in the SortBuf open array parameter:
VAR
BigBuffer : ARRAY[0..500] OF KeyRec;
LilBuffer : ARRAY[0..100] OF KeyRec;
the upper bound of the SortBuf array. In that case, we can set Recs equal to the
high bound of SortBuf, so that ShellSort will find itself passed an array that might
be partly full, or full, but not over-full. (In an over-full array, records in excess of
High(SortBuf) are ignored.) It only takes one simple statement, the first in the
revised ShellSort shown below.
VAR
I,J,K,L : Integer;
Spread : Integer;
VAR
T : KeyRec;
BEGIN
T := RR;
RR := SS;
SS := T
END;
BEGIN
{ First we make sure Recs isn’t higher than the upper bound: }
IF Recs > High(SortBuf) THEN Recs := High(SortBuf);
Spread := Recs DIV 2; { First Spread is half record count }
WHILE Spread > 0 DO { Do until Spread goes to zero: }
BEGIN
FOR I := Spread + 1 TO Recs DO
BEGIN
J := I - Spread;
WHILE J > 0 DO
BEGIN { Test & swap across the array }
L := J + Spread;
IF SortBuf[J].KeyData <= SortBuf[L].KeyData THEN J := 0 ELSE
KeySwap(SortBuf[J],SortBuf[L]);
J := J - Spread
END
END;
Spread := Spread DIV 2 { Halve Spread for next pass }
END
END;
258 FreePascal from Square One, Volume 1
There are some restrictions on open array parameters. You cannot assign to an
open array parameter in its entirety; that is, you cannot treat the array as a whole
in any way. You can only work with an open array parameter on an element-by-
element basis. Open array parameters passed by value (that is, without the VAR
reserved word) are allocated on the stack, and can crash your machine if they take
more stack than you’ve allocated for stack use.
VAR
I : Integer;
BEGIN
FOR I := 1 TO Length(Target) DO
Target[I] := UpCase(Target[I]);
END;
Inside the procedure, the standard Length string function works the way it does
on any sort of string. High(Target) would return the defined length of the actual
parameter passed in Target. Low(Target) will always return 0, since strings are
always zero-based arrays.
Somewhat oddly, the identifier OpenString will not act this way when it is
used to declare a value parameter (that is, without VAR.) As a value parameter,
OpenString yields a string parameter that is always of type STRING, that is, the
maximum string length of 255, and the High function will always return 255. You
should only use OpenString as a VAR parameter!
command toward the top of your source code file, VAR parameters declared using
the STRING reserved word act as open string parameters, and not simply as strings
with a maximum length of 255. However, value parameters of type STRING remain
type STRING and do not become open array parameters. As with OpenString,
the VAR has to be there! This was done to provide backward compatibility to older
Turbo Pascal code that used STRING as a way of safely passing strings of any size
to a procedure or function. If you’re writing new code, the advised method is to use
the OpenString predefined type instead.
Or not: Note well that the {$P} switch works only for short strings. It has no effect
on code making use of ANSIStrings. The better path today is to use old-style short
strings for compatibility with old code only, and use ANSIStrings for all new work.
10.5. Recursion
Recursion is one of those peculiar concepts that seems to defy understanding
totally, and depend completely on mystery for its operation, until eventually some
small spark of understanding happens, and then, wham! It becomes simple or even
obvious. A great many people have trouble understanding recursion at first glance,
so if you do too, don’t think less of yourself for it. For the beginner recursion is
simple. But it is not obvious.
Recursion is what we call it when a function or procedure invokes itself. It
seems somehow intuitive to beginners that having a procedure call itself is either
impossible or else an invitation to disaster. Both of these fears are unfounded, of
course. Let’s look at them both.
Recursion is indeed possible. In fact, having a procedure call itself is no different
from a coding perspective as having a procedure call any other procedure. What
happens when a procedure calls another procedure? Only this: First, the called
procedure is “instantiated;” that is, its formal parameters and local variables are
allocated on the system stack. Next, the return address (the location in the code from
which the procedure was called and to which it must return control) is “pushed”
onto the system stack. Finally, control is passed to the called procedure’s code.
When the called procedure is finished executing, it retrieves the return address
from the system stack and then clears its variables and formal parameters off the
stack by a process we call “popping.” Then it returns control to the code that called
it by branching to the return address.
None of this changes when a procedure calls itself. Upon a recursive call to itself,
new copies of the procedure’s formal parameters and local variables are instantiated
on the stack. Then control is passed to the start of the procedure again.
260 FreePascal from Square One, Volume 1
The problem shows up when execution reaches the point in the procedure
where it calls itself. A third instance of the procedure is allocated on the stack, and
the procedure begins running again. A fourth instance, and a fifth...and after a few
hundred recursive calls the stack has grown so large that it collides with something
important in memory, and the system crashes. If you had this kind of procedure,
such a thing would happen very quickly:
PROCEDURE Fatal;
BEGIN
Fatal
END;
Such a situation is a sort of unlimited software feedback loop. It’s this possibility
that makes newcomers feel uneasy about recursion.
Obviously, the important part of recursion is knowing when to stop.
A recursive procedure must test some condition before it calls itself, to see
if still needs to call itself in order to complete its work. This condition could be
a comparison of a counter against a predetermined number of recursive calls, or
some Boolean condition that becomes true (or false) when the time is right to stop
recursing and go home. When controlled in this way, recursion becomes a very
powerful and elegant way to solve certain programming problems.
Let’s go through a simpleminded example of a controlled recursive procedure.
Read through this program’s code very carefully:
PROGRAM PushPop;
CONST
Levels = 5;
VAR
Depth : Integer;
BEGIN
Writeln(‘Push!’);
Writeln(‘Our depth is now: ‘,Depth);
Depth := Depth +1;
IF Depth <= Levels THEN Dive(Depth);
Writeln(‘Pop!’);
END;
Procedures and Functions 261
BEGIN
Depth := 1;
Dive(Depth);
Write(‘Press any key to exit: ‘);
Readln;
END.
The program itself is nothing more than setting a counter to one and calling the
recursive procedure Dive. Note the constant named Levels. Dive displays the word
“Push!” when it begins executing, and the word “Pop!” when it ceases executing. In
between, it displays the value of the variable Depth and then increments it.
If, at this point, the value of Depth is less than the constant Levels, Dive calls
itself. Each call to Dive increments Depth by one, until at last Depth is greater than
Levels. Then recursion stops.
Running program PushPop produces the output below. Can you tell yourself
exactly why? (Note that the keypress prompt is not included on this page for
simplicity’s sake.)
Push!
Our depth is now 1
Push!
Our depth is now 2
Push!
Our depth is now 3
Push!
Our depth is now 4
Push!
Our depth is now 5
Pop!
Pop!
Pop!
Pop!
Pop!
Follow the execution of PushPop through, with a pencil to touch each keyword,
if necessary, until the output makes sense to you.
5! = 5 * 4 * 3 * 2 * 1
A little scrutiny here will show that 5! is the same as 5 * 4!, and 4! is the same as 4 *
3!, and so on. In the general case, N! = N * (N-1)! Whether you see it immediately or
not, we have already expressed the factorial algorithm recursively by defining it in
terms of a factorial. This will become a little clearer when we express it in Pascal:
BEGIN
IF N > 1 THEN Factorial := N * Factorial(N-1)
ELSE Factorial := 1
END;
And that’s it. We express it as a conditional statement because there must always
be something to tell the code when to stop recursing. Without the N > 1 test the
function would merrily decrement N down past zero and recurse away until the
system crashed.
The way to understand this function is to work it out for N=1, then N=2, N=3,
and so on. For N=1 the N > 1 test returns False, so is assigned the value 1. No
recursion involved. 1! = 1. For N=2 a recursive call to Factorial is made: Factorial
is assigned the value 2 * Factorial(1). As we saw above, Factorial(1) = 1. So 2! = 2
* 1, or 2. For N=3, two recursive calls are made: Factorial is assigned the value 3 *
Factorial(2). Factorial(2) is computed (as we just saw) by evaluating (recursively)
2 * Factorial(1). And Factorial(1) is simply = 1. Catching on? One interesting thing
to do is add (temporarily) a Writeln statement to Factorial that displays the value
of N at the beginning of each invocation.
A sidenote on the power of factorials: Calculating anything over 7! will overflow
a two-byte integer. This is why the Factorial function returns a LongInt parameter.
Here’s an interesting exercise for you: How high a value can you pass in N without
overflowing a long integer?
{->>>>QuickSort<<<<--------------------------------------------}
{ }
{ Filename : QUIKSORT.SRC -- Last Modified 10/30/2016 }
{ }
{ This is your textbook recursive quicksort on an array of key }
{ records, which are defined as the type show below: }
{ }
{ KeyRec = RECORD }
{ Ref : Integer; }
{ KeyData : String30 }
{ END; }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
VAR
T : KeyRec;
BEGIN
T := RR;
RR := SS;
SS := T
END;
VAR
I,J : Integer;
Pivot : KeyRec;
BEGIN
{ Can’t sort if Low is greater than or equal to High... }
IF Low < High THEN
BEGIN
I := Low;
J := High;
Pivot := SortBuf[J];
REPEAT
WHILE (I < J) AND (SortBuf[I].KeyData <= Pivot.KeyData)
DO I := I + 1;
WHILE (J > I) AND (SortBuf[J].KeyData >= Pivot.KeyData)
DO J := J - 1;
264 FreePascal from Square One, Volume 1
BEGIN
DoSort(1,Recs);
END; { QuickSort }
This is done by scanning the array from both ends toward the middle by counters
I and J. I scans from the low end upward; J from the high end downward. The I
counter samples each element, and stops when it finds an element whose value is
higher than the pivot value. Then the scan begins from the top end down, with the
J counter looking for a value that is less than the pivot value. When it finds one, the
two found elements are swapped, thus putting them on the proper side of the pivot
value.
When I and J collide in the middle somewhere (not necessarily in the center!) the
array has been partitioned into two groups of elements: One that is larger than the
pivot value, and one that is smaller than the pivot value. These two groups are not
necessarily equal in size. In fact, they usually will not be. The only thing that is certain
is that all the elements in one group are less than the value of the pivot element, and
all of the elements of the other group are greater than the pivot element. The two
groups are sorted with respect to one another: All elements of the low group are less
than all elements of the high group.
Enter recursion: This same process is now applied to each of the two groups by
calling DoSort recursively for each group. A new pivot value is chosen for each
group, and each group is partitioned around its pivot value, just as the entire array
was originally. When this is done, there are four groups. A little thought will show
you that low-valued elements of the array are being driven toward the low end of the
array, and high-valued elements are being driven toward the high end of the array.
Within each group there is no guarantee that the elements are in sorted order. What
you must understand is that the groups themselves are in sort order. In other words, all
the elements of one group are greater than all the elements of the group below it.
Pressing on: Each of the four groups is partitioned again by more recursive calls
to DoSort. The groups are smaller. Each group taken as one is sorted with respect to
all other groups. With each recursive call, the groups have fewer and fewer members.
In time, each group will contain only one element. Since groups are always in sort
order, if each group is a single element, then all elements of the array are in sorted
order, and QuickSort’s job is finished.
How does QuickSort know when to stop recursing? The first conditional test in
DoSort does it: If Low is greater than or equal to High, the sort is finished. Why?
Because Low and High are the bounds of the group being partitioned. If Low =
High, each of the two groups has only one member. When the groups have only
one member, the array is in sort order and the work is done.
If this makes your head spin, you’re in good company. Follow it through a few
times until it makes sense. Once you can follow QuickSort’s internal logic, you
266 FreePascal from Square One, Volume 1
{--------------------------------------------------------------}
{ SortTest }
{ }
{ Data sort demonstration program }
{ }
{ by Jeff Duntemann }
{ FreePascal V3.0 }
{ Last update 10/30/2016 }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
PROGRAM SortTest;
CONST
Highlite = True; { These first 4 constants are used by WriteAt }
NoHighlite = False;
NoCR = False;
Shell = True; { Which sort procedure will we be using? }
Quick = False;
TYPE
String30 = STRING[30];
KeyRec = RECORD
Procedures and Functions 267
Ref : Integer;
KeyData : String30
END;
VAR
IVal : Integer; { Holds integer value for user’s response }
WorkArray : KeyArray;
Randoms : KeyFile; { Files should generally be declared global }
BEGIN
Assign(Randoms,’randoms.key’);
Rewrite(Randoms);
FOR I := 1 TO KeyQuantity DO
BEGIN
FillChar(WorkKey,SizeOf(WorkKey),0);
FOR J := 1 TO SizeOf(WorkKey.KeyData)-1 DO
WorkKey.KeyData[J] := Chr(Pull(65,90));
WorkKey.KeyData[0] := Chr(30);
Write(Randoms,WorkKey);
END;
Close(Randoms)
END;
PROCEDURE DisplayKeys;
BEGIN
Assign(Randoms,’randoms.key’);
Reset(Randoms);
Window(25,13,70,22);
GotoXY(1,1);
WHILE NOT EOF(Randoms) DO
268 FreePascal from Square One, Volume 1
BEGIN
Read(Randoms,WorkKey);
IF NOT EOF(Randoms) THEN Writeln(WorkKey.KeyData)
END;
Close(Randoms);
Writeln;
Writeln(‘ >>Press (CR)<<’);
Readln;
ClrScr;
Window(1,1,80,25)
END;
BEGIN
Assign(Randoms,’randoms.key’);
Reset(Randoms);
Counter := 1;
WriteAt(20,15,NoHighlite,NoCR,’Loading...’);
WHILE NOT EOF(Randoms) DO
BEGIN
Read(Randoms,WorkArray[Counter]);
Counter := Succ(Counter)
END;
Close(Randoms);
Write(‘...sorting...’);
IF Shell THEN ShellSort(WorkArray,Counter-1)
ELSE QuickSort(WorkArray,Counter-1);
Write(‘...writing...’);
Rewrite(Randoms);
FOR I := 1 TO Counter-1 DO Write(Randoms,WorkArray[I]);
Close(Randoms);
Writeln(‘...done!’);
WriteAt(-1,21,NoHighlite,NoCR,’>>Press (CR)<<’);
Readln;
ClearRegion(2,15,77,22)
END;
BEGIN
ClrScr;
MakeBox(1,1,80,24,PCLineChars);
WriteAt(18,3,HighLite,NoCR,’FreePascal From Square One Sort Demo’);
REPEAT
WriteAt(25,5,NoHighlite,NoCR,’[1] Generate file of random keys’);
WriteAt(25,6,NoHighlite,NoCR,’[2] Display file of random keys’);
WriteAt(25,7,NoHighlite,NoCR,’[3] Sort file via Shell sort’);
WriteAt(25,8,NoHighlite,NoCR,’[4] Sort file via Quicksort’);
Procedures and Functions 269
TYPE
RecPtr = ^DynaRec;
DynaRec = RECORD
DataPart : String;
Next : RecPtr
END;
Ignore for now what ^DynaRec means if you haven’t been exposed to pointer
notation before. What’s significant here is that the FreePascal compiler “takes our
word” that we will, in fact, define DynaRec before the program ends, and thus allows
the definition of RecPtr before the definition of DynaRec—even though DynaRec
is an integral part of the definition of RecPtr. Using an undefined identifier before
its declaration is called a forward reference.
The context of defining pointer types is just about the only context in which
Pascal will accept a forward reference to a type. (It’s also true of object classes, which
I won’t be covering in this introductory volume.) In certain circumstances, however,
Pascal can be persuaded to accept a procedure or function identifier before that
procedure or function has been defined. In a sense, we must promise the compiler
that we will, in fact, define the identifier. Or, if you prefer, we have to declare that we
270 FreePascal from Square One, Volume 1
will declare such an identifier somewhere down the source code trail.
This promise is called a forward declaration. It is accomplished with a Pascal reserved
word, FORWARD, and it is done this way:
PROCEDURE NotThereYet(Foo,Bar : Integer); FORWARD;
What we have here is the procedure header all by itself, without any procedure
body or local declarations of constants, types, or variables. People who understand
units (See Chapter X) will think that this resembles the declarations in the interface
section of a unit, and they are right.
Later on in the program, sometime before the BEGIN that marks the start of the
main program block, procedure NotThereYet must be declared in its entirety. If it
isn’t, the compiler will hand us an error.
The eventual declaration of the procedure is perfectly ordinary. No special syntax
indicates that the procedure had earlier been declared as FORWARD.
You do have the option of not re-declaring the forward declared procedure’s
parameter list. In other words, you could in fact define an (empty) procedure
NotThereYet without parameters:
PROCEDURE NotThereYet;
BEGIN
END;
This shorthand, also, will be familiar to people who understand units. I consider
it bad practice, however. The compiler allows you to re-declare the parameters,
and re-declaring parameters contributes to the clarity of the program to have the
parameter list in both places: The forward declaration, (where they are essential)
and the full definition.
At last we come to the question: What good is all of this? The answer is: Not much.
The only situation that genuinely requires forward declaration is circular (also called
mutual) recursion. Consider these two procedure definitions:
PROCEDURE Egg;
BEGIN
.
.
Chicken;
.
.
END;
Procedures and Functions 271
PROCEDURE Chicken;
BEGIN
.
.
Egg;
.
.
END;
Which comes first? You can’t declare Chicken without calling Egg, and you
can’t declare Egg without calling Chicken. Pascal will call foul on the whole thing
unless you forward declare one or the other. Adding the forward declaration makes
everything work:
PROCEDURE Egg;
BEGIN
. { Other program logic keeps this from }
. { being an infinite loop. }
Chicken; { Without the FORWARD, we get an error here. }
.
.
END;
PROCEDURE Chicken;
BEGIN
.
.
Egg;
.
.
END;
That’s what circular recursion is, but what it’s good for has thus far escaped
me. My hunch is that any program that appears to require circular recursion can
probably be rewritten a different way without it.
I look upon the reserved word FORWARD much as I do the spokeshave sitting
in the bottom drawer of my tool cabinet. I have never yet used it, but by golly, if I
ever need to shave some spokes, I know just where it is.
272 FreePascal from Square One, Volume 1
Chapter 11.
Standard Functions
273
274 FreePascal from Square One, Volume 1
integer value.) But the reverse is not true, since a Real value may have a decimal
part, and there is no way to express a decimal part in type Integer. Round and
Trunc give us our choice of two ways to “transfer” a Real value into an Integer
value. Round and Trunc both accept parameters of type Real and return values
that may be assigned to either type Integer or Real.
Round
In mathematics, rounding a number means moving its value to the nearest integer.
This is the job done by Round. Round(X) returns an integer value that is the integer
closest to X. The direction in which a real number with a fractional part is rounded
is usually given as “up” or “down”. This can be confusing when you start dealing
with negative real numbers. I prefer to visualize a number line and speak of “toward
zero” or “away from zero.”
• • For X greater than 0: Rounds away from zero (up) for fractional parts
greater than or equal to .5. Rounds toward zero (down) for fractional parts
less than .5.
• • For X less than 0: Rounds away from zero (down) for fractional parts
greater than or equal to .5. Rounds toward zero (up) for fractional parts less
than .5.
Some examples:
Round(4.449) { Returns 4 }
Round(-6.12) { Returns -6 }
Round(0.6) { Returns 1 }
Round(-3.5) { Returns -4 }
Round(17.5) { Returns 18 }
Because the way rounding works with Round is symmetric with respect to zero,
Round(-X) is equal to -Round(X).
When Round is used with a parameter that is precisely halfway between two
consecutive integers (for example, 3.5) the function uses banker’s rounding, which
rounds to the nearest even number. Round would take the parameter 3.5 and return
4; it would take the value 4.5 and also return 4.
Note that using Round(X) for a real value X that cannot be expressed as a long
integer will generate a range check runtime error. This will occur if range checking
is enabled; if range checking is not enabled, your program will continue executing,
but the value actually assigned to the integer will be unpredictable, but predictably
meaningless.
Standard Functions 275
Trunc
Truncating a real number simply means removing its fractional part and dealing
with what’s left. Trunc(X) returns the closest integer value toward zero—and if you
ponder that for a moment you’ll see that it is equivalent to removing the fractional
part and calling the whole number part an integer. Examples:
Trunc(17.667) { Returns 17 }
Trunc(-3.14) { Returns -3 }
Trunc(6.5) { Returns 6 }
Trunc(-229.00884) { Returns -229 }
Trunc returns a value of type LongInt. There were various range issues connected
with Trunc in early versions of Turbo Pascal, relating to the lack of a LongInt type
in Turbo Pascal 3.0 and earlier. (Long integers first appeared in Turbo Pascal 4.0.)
Assuming the type to which you assign the value returned by Round or Trunc has
the range to contain the value, there will be no problems with FreePascal.
VAR
I : Integer;
R : Real;
I := 64;
R := 6.077;
BEGIN
Hypotenuse := Sqrt(Sqr(Side1) + Sqr(Side2))
END;
All of these additional trig functions reside in the Math unit, and to use them you
must place Math in your USES statement.
Note that even though the X and Y parameters passed to FreePascal’s trig
functions are declared as type Real, the compiler will allow you to pass an integer-
type literal or variable in X without error, and will treat the value as a real number
without a fractional part during the calculations.
Absolute value
Absolute value in mathematics is the distance of a number from zero. In practical
terms, this means stripping the negative sign from a negative number and leaving a
positive number alone. The Pascal function Abs(X) returns the absolute value of X.
The parameter X may be type Real or Integer. The type of the returned value is the
same as the type of X. For example:
Natural logarithms
There are two Pascal standard functions that deal with natural logarithms. Natural
logarithms are mathematical functions that turn on a remarkable irrational number
named e, which, to six decimal places, is 2.718282. Explaining where e comes from,
or explaining natural logarithms in detail, is outside the scope of this book. Do read
up on them (in any senior high math text) if the concept is strange to you.
Exp(X) returns the exponential function for X. The parameter may be a real
278 FreePascal from Square One, Volume 1
number or an integer, but the returned value is always real. The exponential
function raises e to the X power. Therefore, when you evaluate Exp(X), what
you are actually evaluating is eX .
Ln(X) returns the natural logarithm (logarithm to the base e) of X. The
parameter may be type Integer or Real, and the returned value, again, is always
type Real. The sense of the Ln(X) function is the reverse of Exp(X): Evaluating
Ln(X) yields the exponent to which e must be raised to give X.
Exponentiation
Natural logarithms are the most arcane of Pascal’s mathematical standard
functions. They are most used in mathematics that many of us would consider
“heavy.” However, there is one use for which natural logarithms fill an enormous
hole in Standard Pascal’s definition: Exponentiation. Unlike nearly all other
programming languages, Pascal contains no general function for raising X
to the Yth power. (In FORTRAN the exponentiation operator is the double
asterisk: X**Y raises X to the Yth power.) Exp and Ln allow us to create a
Standard Pascal function that raises one number to a given power:
BEGIN
Power := Exp(Ln(Mantissa)*Exponent)
END;
This almost certainly looks like magic unless you really understand how natural
logarithms work. Two cautions: The result returned is type Real, not Integer. Also,
do not pass a zero or negative value in Mantissa. A runtime error will be triggered.
Reason: Ln(X) for a negative X is undefined!
But that’s Standard Pascal. Starting with version 4, Delphi added a new function
to its Math unit: Power. FreePascal also includes Power in its Math unit:
FUNCTION Power(Mantissa, Exponent: Real) : Real
And that’s not the end of it. FreePascal implements an exponention operator as
well as an exponentiation function. The operator is the double asterisk, the same
one used by FORTRAN. As with the Power function, the exponentiation operator
Standard Functions 279
is stored in the Math unit, and you must include Math in your program to use it.
(Math is not included in USES by default.) With the exponentiation operator, you
raise a value to a power this way:
I := J**4;
This statement raises the value in J to the fourth power, and stores the result in I. As
you might imagine, if you raise a real number value to a power, the result must be
returned to a variable of type Real.
As far as I know, the double asterisk operator for exponentiation is used in no
other Pascal implementation but FreePascal.
Ord
As its name suggests, Ord(X) deals with ordinal types. Ordinal types are those
types that can be “enumerated;” that is, types with a fixed number of values in a
well-defined order. Ord(X) returns the ordinal position (an integer) of the value
X in its ordinal type. The sixty-sixth character in the ASCII character set is the
capital letter ‘A’. Ord(‘A’) returns 65. The third color in our old friend type
Spectrum is Yellow. Ord(Yellow) returns 2—remember (for both examples)
that we start counting at 0!
Chr
Chr goes in the opposite direction from Ord: Chr(X) returns a character value
corresponding to the Xth character in the ASCII character set. (X here is an integer.)
Chr(65) returns the capital ‘A’. Chr(66) returns capital letter ‘B’, and so on. Don’t
try to pass Chr an integer value higher than 255. The results will be undefined, and
probably garbage.
The most important use of Chr(X) is generating character values that are not
expressed by any symbol that you can place between single quote marks. How do
you put a line feed in quotes? Or worse yet, a bell character? You don’t—you express
them with Chr :
280 FreePascal from Square One, Volume 1
BEGIN
Lowercase := [‘a’..’z’];
FOR I := 1 TO Length(Target) DO
IF Target[I] IN Lowercase THEN
Target[I] := Chr(Ord(Target[I]) - 32)
END;
FreePascal has a built-in procedure, UpCase, which will accomplish the same
thing as the expression
Chr(Ord(Target[I]) - 32)
in the procedure above. However, the expression is a good example of the use of
Chr and Ord, one right beside the other!
Pred(43) { Returns 42 }
Succ(19210) { Returns 19211 }
Pred(Orange) { Returns Red }
Succ(Green) { Returns Blue }
Pred(Red) { Undefined! }
This last example bears a closer look. The predecessor value of Red is undefined.
Recall the definition of our enumerated type Spectrum:
Spectrum = (Red,Orange,Yellow,Green,Blue,Indigo,Violet);
Red is the very first value in the type. There is nothing before it, so Pred(Red) makes
no sense in the context of type Spectrum. Similarly, Succ(Violet) makes no sense,
since there is no value in Spectrum after Violet.
Pred(<value>) of the first value of an ordinal type is undefined. Succ(<value>)
of the last value of an ordinal type is undefined.
Pred and Succ provide a means of “stepping through” an ordinal type to do some
repetitive manipulation on a range of the values in that ordinal type. For example,
in printing out the names on a telephone/address list, we might want to put a little
header before the list of names beginning with ‘A’, and then before the list of names
beginning with ‘B’, and so on. Assuming that the names are stored in sorted order
in an array, we might work it this way:
VAR
Names : ARRAY[1..200] OF String[35];
NameCount : Integer;
BEGIN
Write(LST,Chr(13),Chr(10));
Writeln(LST,’[‘,Ch,’]-------------------------------’)
END;
VAR I : Integer;
Ch : Char;
AName : String[35];
BEGIN
Ch := ‘A’;
Header(Ch);
282 FreePascal from Square One, Volume 1
FOR I := 1 TO NameCount DO
BEGIN
AName := Names[I];
IF AName[1] <> CH THEN
REPEAT
CH := Succ(CH);
Header(Ch)
UNTIL AName[1] = CH;
Writeln(LST,AName)
END
END;
Assume that the array Names has been filled somehow with names, and that the
number of names has been placed in NameCount. The names must be in sorted
order, last name first. When PrintBook is invoked, the list of names in Names is
printed on the system printer, with a header for each letter of the alphabet:
[A]-------------------------------
Albert*Eddie
Aldiss*Brian
Anthony*Piers
[B]-------------------------------
Brooks*Bobbie
Bentley*Mike
[C]-------------------------------
Chan*Charlie
Charles*Ray
Cabell*James Branch
[D]-------------------------------
[E]-------------------------------
[F]-------------------------------
Farmer*Philip Jose
Flor*Donna
and so on. Letters for which no names exist in the list will still have a printed header
on the list. The printed listing, if cut into memo-book sized sheets, would make the
core of a “little black book” for names and addresses.
Look at the listing of PrintBook. Ch is given an intial value of A. As the names
are printed, the first letter of the each name is compared to the letter stored in Ch. If
they don’t match, the loop
REPEAT
Standard Functions 283
Ch := Succ(Ch);
Header(Ch)
UNTIL AName[1] = Ch;
is executed. The letter in Ch is “stepped” along the alphabet until it “catches up” to
the first letter in AName. For each step along the alphabet, a header is printed.
We might have written Ch := Chr(Ord(Ch)+1) instead of Ch := Succ(Ch).
Succ provides a much crisper notation. And because Chr does not work with
enumerated types, Succ is the only way to step along the values of a programmer-
defined enumerated type like Spectrum.
11.7. Odd
The last of the standard functions from ISO Standard Pascal that we’ll talk about is
a transfer function: Odd(X). Here, X is an integer value. If X is an odd value Odd(X)
returns the Boolean value True. If X is an even value, Odd(X) returns False.
Odd is thus a way of expressing an integer value as a value of type Boolean.
Any even number can express the value False, and any odd number can express
the value True. Although it doesn’t fit the classic definition of “even” as “divisible by
two,” 0 is considered an even number by virtue of lying between two odd numbers,
1 and -1.
11.9. Pi
284 FreePascal from Square One, Volume 1
VAR
RS : Single;
RR : Real;
RD : Double;
RE : Extended;
RC : Comp;
Note that type Comp can accept a value from Pi without triggering an error, but
will not store the fractional part!
Functionally, they are equivalent to Pred and Succ except that Pred and Succ are
functions rather than procedures.
In other words, Inc and Dec may stand alone as statements:
VAR
I : Integer;
Ch : Char;
I := 17;
Ch := ‘A’;
Inc(I); { I now contains 18 }
Dec(Ch); { Ch now contains ‘@’ }
Pred and Succ, by contrast, need to be placed on the right side of an assignment
statement, either alone or inside of an expression:
I := 17;
Ch := ‘A’;
I := Succ(I); { I now contains 18 }
Ch := Pred(Ch); { Ch now contains ‘@’ }
Why use Inc and Dec if the standard and much more portable Pred and Succ
are available? Only one reason: speed. In most FreePascal platforms, Inc and
Dec generate faster code than Pred and Succ. Why this is so lies in the details
of compiler code generation, and so is beyond the scope of this book. In truth,
on modern CPUs the difference won’t become apparent unless you’re making a
lot more use of the functions than any ordinary program is ever likely to. Only if
your code may need to be compiled with a different compiler will Pred and Succ
have a distinct advantage.
might display:
7.0172090270E-01
286 FreePascal from Square One, Volume 1
7.3332131305E-01
8.0977424840E-01
6.7220290820E-01
9.2550002318E-01
The E-01 exponent makes all these numbers fall in the range 0.0 - 0.9999999999.
All random numbers returned by the function Random fall within this range, but
of course if you need random real numbers in another range, you need only shift
the decimal point the required number of places to the right.
Random(I) returns random integers. The parameter is an integer that sets an
upper bound for the random numbers returned by the function. Random(I) will
return a number greater than or equal to zero and less than I. I may be any integer
up to the maximum value that the integer type used for I can express. Although you
can pass a negative number to Random without triggering an error, the returned
value will always be the maximum value expressible in that integer type. There is no
way to make Random(I) return negative random numbers.
There is frequently a need for a random number in a particular range, say between
15 and 50 or between 100 and 500. The procedure Pull shown below meets this
need by extracting random integers until one falls in the range specified by Low
and High.
VAR
I : Integer;
BEGIN
REPEAT { Keep requesting random integers until }
I := Random(High + 1); { one falls between Low and High }
UNTIL I >= Low;
Pull := I
END;
Standard Functions 287
Randomize
The Randomize procedure exists because FreePascal’s random numbers, like all
random numbers generated in software, are not truly random but only pseudorandom,
which means that a series of such numbers approximates randomness. The real
issue is that a series of pseudorandom numbers may well repeat itself each time the
program is run, unless the random number generator is “reseeded” with a new seed
value. This is the job of Randomize.
Internally, what Randomize does is calculate a seed value from the computer’s
system clock, and place it in a predefined system variable called RandSeed. You
don’t have to access RandSeed at all. Each time you call Randomize, RandSeed
gets a new seed value based on the current value in the system clock, which changes
constantly.
Randomize should be called at least once in every program that makes use of
random numbers, and to make your pseudorandom numbers more nearly random,
might be called each time you want a new random number or series of random
numbers.
A dice game
The following program shows one use of random numbers in a game situation.
Rollem simulates the roll of one or more dice—up to as many as will fit across the
screen. Procedure Roll may be placed in your function/procedure library and used
in any game program that must roll dice in a visual manner. It will display a number
of text-mode dice at location X,Y on the screen, where X,Y are the coordinates of
the upper left corner of the first die. The NumberOfDice parameter tells Roll how
many dice to roll; there is built-in protection against attempting to display more
dice than space to the right of X will allow.
Rollem is also a good exercise in REPEAT/UNTIL loops. The MakeBox
procedure was described in Section 10.2, as part of the BoxTest program..
{--------------------------------------------------------------}
{ Rollem }
{ }
{ A dice game to demonstrate random numbers and box draws }
{ }
{ by Jeff Duntemann }
{ FreePascal 3.0.4 }
{ Last update 2/24/2018 }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
288 FreePascal from Square One, Volume 1
PROGRAM Rollem;
USES Crt,BoxStuff;
CONST
DiceFaces : ARRAY[0..5,0..2] OF STRING[5] =
((‘ ‘,’ o ‘,’ ‘), { 1 }
(‘o ‘,’ ‘,’ o’), { 2 }
(‘ o’,’ o ‘,’o ‘), { 3 }
(‘o o’,’ ‘,’o o’), { 4 }
(‘o o’,’ o ‘,’o o’), { 5 }
(‘o o o’,’ ‘,’o o o’)); { 6 }
TYPE
String80 = String[80];
VAR
I : Integer;
Quit : Boolean;
Dice : Integer;
DiceX : Integer;
Ch : Char;
Banner : String80;
BEGIN
IF (NumberOfDice * 9)+X >= 80 THEN { Too many dice horizontally }
NumberOfDice := (80-X) DIV 9; { will scramble the CRT display! }
FOR I := 1 TO NumberOfDice DO
BEGIN
XOffset := (I-1)*9; { Nine space offset for each die }
MakeBox(X+XOffset,Y,7,5,PCLineChars); { Draw a die }
Throw := Random(6); { “Toss” it }
FOR J := 0 TO 2 DO { and fill it with dots }
BEGIN
GotoXY(X+1+XOffset,Y+1+J);
Write(DiceFaces[Throw,J])
END
END
END;
Standard Functions 289
BEGIN
Randomize; { Seed the pseudorandom number generator }
ClrScr; { Clear the entire screen }
Quit := False; { Initialize the quit flag }
Banner := ‘GONNA Roll THE BONES!’;
MakeBox(-1,1,Length(Banner)+4,3,PCLineChars); { Draw Banner box }
GotoXY((80-Length(Banner)) DIV 2,2); Write(Banner); { Put Banner in it }
REPEAT
REPEAT
FOR I := 6 TO 18 DO { Clear the game portion of screen }
BEGIN
GotoXY(1,I);
ClrEol
END;
GotoXY(1,6);
Write(‘>>How many dice will we Roll this game? (1-5, or 0 to exit):
‘);
Readln(Dice);
IF Dice = 0 THEN Quit := True ELSE { Zero dice sets Quit flag }
IF (Dice < 1) OR (Dice > 5) THEN { Show error for dice out of range }
BEGIN
GotoXY(1,23);
Write(‘>>The legal range is 1-5 Dice!’)
END
UNTIL (Dice >= 0) AND (Dice <= 5);
GotoXY(1,23); ClrEol; { Get rid of any leftover error messages }
IF NOT Quit THEN { Play the game! }
BEGIN
DiceX := (80-(9*Dice)) DIV 2; { Calculate centered X for dice }
REPEAT
GotoXY(1,16); ClrEol;
Roll(DiceX,9,Dice); { Roll & draw dice }
GotoXY(1,16); Write(‘>>Roll again? (Y/N): ‘);
Readln(Ch);
UNTIL NOT (Ch IN [‘Y’,’y’]);
GotoXY(1,18); Write(‘>>Play another game? (Y/N): ‘);
Readln(Ch);
IF NOT (Ch IN [‘Y’,’y’]) THEN Quit := True
END
UNTIL Quit { Quit flag set ends the game }
END.
290 FreePascal from Square One, Volume 1
Chapter 12.
String Functions
T he earliest versions of Pascal, including ISO Standard Pascal, had no clean and
easy way to deal with text in a program. Virtually all Pascal compilers since then
have implemented strings that go well beyond the crude Packed Array of Characters
(PAOC) string type that was all Standard Pascal had to offer. The primary method
of string representation in FreePascal goes all the way back to UCSD Pascal in 1978,
and has evolved through the several versions of Turbo Pascal and Delphi.
We looked at FreePascal’s string data types in some detail in Section 8.6. I’ll
only provide a quick recap here: A variable of type STRING is actually an array
of characters with a counter attached at the beginning to keep tabs on how many
characters have been loaded into the array. The original Turbo Pascal type STRING
had a physical length of 255 characters. In FreePascal, there are two major string
types: ShortString (which is basically Turbo Pascal’s original STRING type) and
ANSIString, which can be as long as 4,294,967,295 characters. ANSIString is the
default string type. In other words, when you define a variable as type STRING, it
will be created as an ANSIString variable. If you want to use the older ShortString
type, you have to explicitly declare a variable as type ShortString.
The $H compiler directive changes the default: $H+ makes the default string type
ANSIString, and $H- makes the default ShortString.
12.1. Length
The length counter of a string variable is accessed with the predefined string function
Length, which is defined this way:
FUNCTION Length(Target : STRING) : Integer;
Length returns the value of the length counter as type Integer, which indicates the
logical length of string Target at any given time. Note that the length of an empty
string is 0.
291
292 FreePascal from Square One, Volume 1
Counter := Length(MyText);
In the old days, many people read the length counter of a short string by
examining element 0 of the string:
Counter := Ord(MyText[0]);
The transfer function Ord is necessary here, because a string is an array of characters,
including the ShortString length counter. Ord converts the length counter from
type Char to type Integer.
Don’t do this! In the old days there was only one string type, and all strings
treated character 0 as the length counter. FreePascal defaults to the ANSIString
type, which is not as simple as ShortString. There is no “easy” way to read the
length counter of an ANSIString by examining it. Use the Length function only.
The following procedure CapsLock accepts a string parameter and returns it
with all lower-case letters changed to their corresponding upper-case letters. Note
the use of the Length function:
12.2.String Concatenation
Concatenation is the process of taking two or more strings and combining them into
a single string. FreePascal gives you two separate ways to perform this operation.
String Functions 293
The easiest way to concatenate two or more strings is to use FreePascal’s string
concatenation operator (+). Many BASIC interpreters also use the plus symbol to
concatenate strings. Simply place the string variables in order, separated by the
string concatenation operator:
BigString := String1 + String2 + String3 + String4;
Variable BigString should, of course, be large enough to hold all the variables you
intend to concatenate into it. If the total length of all the source strings is greater
than the physical length of the destination string, all data that will not fit into the
destination string is truncated off the end and ignored.
The built-in function Concat performs the same function as the string
concatenation operator. It’s considered a “legacy feature” because several older
compilers, including UCSD Pascal, included it. Unless you’re porting (very) old code
to FreePascal, you’re unlikely to need it, and the + operator is a great deal simpler
and more intuitive. The following example shows how both string concatenation
methods work:
VAR
Subject,Predicate,Sentence : String[80];
Here, two string variables and a string literal are concatenated into a single string
variable. The output of the Writeln statement would be the single string “built”
from the smaller strings via concatenation:
Kevin the hacker crashed the system, but brought it up again.
12.3. Delete
Removing one or more characters from a string is the job done by the built-in
Delete procedure, predefined this way:
PROCEDURE Delete(Target : STRING; Pos,Num : Integer);
Delete removes Num characters from the string Target beginning at the character
number passed in Pos. The length counter of Target is updated to reflect the deleted
characters.
294 FreePascal from Square One, Volume 1
VAR
Magic : STRING;
Before the Delete operation, the string Magic has a length of 38 characters. When
run, this example will display:
Watch me make disappear...
BEGIN
WHILE (Length(Target) > 0) AND (Target[1] IN Whitespace) DO
Delete(Target,1,1)
END;
12.4. Pos
Locating a substring within a larger string is handled by the built-in function Pos.
Pos is predefined this way:
FUNCTION Pos(Pattern : <string or char>; Source : STRING) : Integer;
Pos returns an integer that is the location of the first occurrence of Pattern in Source.
Pattern may be a string variable, a Char variable, or a string or character literal.
String Functions 295
For example:
VAR
ChX,ChY : Char;
Little,Big : STRING;
Pos does distinguish between upper and lower case letters. If Pos cannot locate
Pattern in Source, it returns a value of 0.
12.5. Copy
Extracting a substring from within a string is accomplished with the Copy built-in
function. Copy is predefined this way:
FUNCTION Copy(Source : STRING; Pos,Num : Integer) : STRING;
Copy returns a string that contains Num characters from Source, beginning at
character Pos within Source:
VAR
Roland,Tower : STRING;
In this example, Index and Size are passed to Copy as constants. They can also
be passed as integer variables or expressions. The following function accepts a string
containing a file name, and returns a string value containing the file extension. (The
extension is the part of a file name from the period to the end; in “sample.txt” the
extension is “.txt”.)
VAR
DotPos : Integer;
BEGIN
DotPos := Pos(‘.’,FileName);
IF DotPos = 0 THEN GetExt := ‘’ ELSE
GetExt := Copy(FileName,DotPos,(Length(FileName)-DotPos)+1);
END;
GetExt first tests to see if there is, in fact, a period in FileName at all. (File
extensions are optional.) If there is no period, there is no extension, and GetExt
is assigned the null string. If a period is there, Copy is used to assign to GetExt all
characters from the period to the end of the string.
Since the length of a file extension may be 2, 3, or 4 characters, the expression
(Length(FileName)-DotPos)+1 is needed to calculate just how long the extension
is in each particular case. If Index plus Size is greater than the logical length of
Source, Copy truncates the returned string value to whatever characters lie
between Index and the end of the string.
12.6.Insert
A string can be added to the end of another string by using the Concat function.
Copying a string into the middle of another string (and not simply tacking it on at
the end, as with concatenation) is done with the Insert procedure.
Insert is predefined this way:
When invoked, Insert copies Source into Target starting at position Position
within Target. All characters in Target starting at position Position are moved
forward to make room for the inserted string, and Target’s length counter is updated
to reflect the addition of the inserted characters.
String Functions 297
VAR
Sentence,Ozzie : STRING;
If inserting text into Target gives Target more characters than it can physically
contain, Target is truncated to its maximum physical length. Characters that would
fall beyond the physical lenth are lost.
Here’s an example. The $H- compiler switch means that the string variables
Fickle and GOP will be declared as short strings.
$H-
VAR
Fickle,GOP : STRING[18];
Fickle := ‘I am a Democrat.’;
GOP := ‘Republican.’;
Insert(GOP,Fickle,8);
Writeln(Fickle);
This prints:
I am a Republican.
Note in this example that the string “Democrat.” was not overwritten; it was pushed
off the end of string Fickle into nothingness. After the insert, Fickle should have
contained
I am a Republican.Democrat.
12.7. Str
It is important to remember that a number and its string equivalent are not
interchangeable. In other words, the integer 37 and its string representation, the
two ASCII characters ‘3’ and ‘7’ look the same on your screen but are completely
incompatible in all ways but that.
298 FreePascal from Square One, Volume 1
FreePascal provides a pair of procedures for translating numeric values into their
string equivalents, and vise versa. Translating a numeric value to its string equivalent
is done with the procedure Str. Str is predefined this way:
PROCEDURE Str(<formatted numeric value>; VAR ST : STRING);
The formatted numeric value can be either an integer or a real number. It is given as
a write parameter. (See Section X.X for a complete discussion of write parameters
as they apply to all simple data types, numeric and non-numeric.) Briefly, a write
parameter is an expression that gives a value and a format to express it in. The write
parameter I:7 (assuming I was previously declared an integer) right-justifies the
value of I in a field seven characters wide. R:9:3 (assuming R was declared Real
previously) right-justifies the value of R in a field 9 characters wide with three figures
to the right of the decimal place.
The use of Str is best shown by a few examples:
CONST
Bar = ‘|’;
VAR
R : Real;
I : Integer;
TX : STRING[30];
R := 45612.338;
I := 21244;
Str(I:8,TX);
Writeln(Bar,TX,Bar); { Displays: | 21244| }
Str(I:3,TX);
Writeln(Bar,TX,Bar); { Displays: |21244| }
Str(R,TX);
Writeln(Bar,TX,Bar); { Displays: | 4.5612338000E+04| }
Str(R:13:4,TX);
Writeln(Bar,TX,Bar); { Displays: | 45612.3380| }
Note from the third example that if you do not specify any format for a real number,
the default format will be scientific notation in a field eighteen characters wide.
String Functions 299
12.8. Val
Going in the other direction, from string representation to numeric value, is
accomplished by the Val procedure. Val is predeclared this way:
PROCEDURE Val(ST : STRING; VAR <numeric variable>; VAR Code : Integer);
Val’s task is somewhat more complicated than Str’s. For every numeric value there
is a string representation that may be constructed from it. The reverse is not true;
there are many string constructions that cannot be evaluated as numbers. So Val
must have a means of returning an error code to signal an input string that cannot
be evaluated as a number. This is the purpose of the Code parameter.
If the string is evaluated without any problem, Code’s value is 0 and the numeric
equivalent of the string is returned in the numeric variable. If FreePascal finds that
it cannot evaluate the string to a number, Code returns the character position of
the first character that violates the evaluation scheme. The numeric variable in that
case is undefined:
PROGRAM Evaluator;
VAR
SST : STRING;
R : Real;
Result : Integer;
BEGIN
REPEAT
Write(‘>>Enter a number in string form: ‘);
Readln(SST);
IF Length(SST) > 0 THEN
BEGIN
Val(SST,R,Result);
IF Result <> 0 THEN
Writeln
(‘>>Cannot evaluate that string. Check character #’,Result)
ELSE
Writeln
(‘>>The numeric equivalent of that string is ‘,R:18:10)
END
UNTIL Length(SST) = 0
END.
This little program will allow you to experiment with Val and see what it
will accept and what it will reject. One unfortunate shortcoming of Val is that
it considers commas an error. A string like ‘5,462,445.3’ will generate an error
on character #2.
300 FreePascal from Square One, Volume 1
VAR
TName : String;
BEGIN
IF Pos(‘*’,Name) <> 0 THEN
BEGIN
TName := Copy(Name,1,(Pos(‘*’,Name)-1));
Delete(Name,1,Pos(‘*’,Name));
Name := Concat(Name,’ ‘,TName)
END
END;
The theory is simple: If there is no asterisk in the name, it’s something like “Granny
Maria’s Pizza Palace” and needs no reversal. Hence the first test. If an asterisk is
found, the last name up to (but not including) the asterisk is copied from Name
into TName, a temporary string. Then the last name is deleted from Name, up to
and including the asterisk. What remains in Name is thus the first name. Finally,
concatenate TName (containing the last name) to Name with a space to separate
them. The name is now in its proper, first-name-first form.
Obviously, if you try to store and sort on a name of some sort that rightfully
contains an asterisk, the name is going to be mangled by RvrsName.
CONST
Uppercase : SET OF Char = [‘A’..’Z’];
Lowercase : SET OF Char = [‘a’..’z’];
VAR
I : INTEGER;
BEGIN
IF Up THEN FOR I := 1 TO Length(Target) DO
IF Target[I] IN Lowercase THEN
Target[I] := UpCase(Target[I])
ELSE { NULL }
ELSE FOR I := 1 TO Length(Target) DO
IF Target[I] IN Uppercase THEN
Target[I] := Chr(Ord(Target[I])+32);
ForceCase := Target
END;
In FreePascal, functions may return string values the same as any other values.
However, the string type must either be the default type STRING or have been
declared before the declaration of your string function. In other words, if you wish
your function to return a string with a physical length of 80 you must have declared
a string type with that physical length:
TYPE
String80 = STRING[80];
You cannot use the bracketed string-length notation on a string function return
value. That is, you could not have declared ForceCase this way:
In this example, the characters typed after the program name “CASE” constitute
the command line tail:
DOWN B:FOOFILE.TXT
The function ParamCount returns the number of parameters typed after the
command on the operating system command line. Parameters must have been
separated by spaces or tab characters to be considered separate parameters.
Commas, slashes, and other symbols on will not delimit separate parameters!
ParamStr returns a string value that is one of the parameters. The number of
the parameter is specified by ParameterNumber, starting from 1. If you typed
several parameters on the command line, for example:
ParamStr(2)
will return the second parameter. Note that ParamStr[0] is the name of the program
executed with the parameters starting at ParamStr[1].
Keep this in mind: Always read the command line tail before opening your first
disk file! The same area use to store the tail is also used in buffering disk accesses
in some cases using some operating systems. The best way to avoid this sort of
304 FreePascal from Square One, Volume 1
trouble is to keep an array of strings large enough to hold the maximum number of
parameters your program needs, and read the parameters into the array as soon as
your program begins running. This is easy enough to do:
VAR
I : Integer;
ParmArray : ARRAY[1..8] OF STRING[80];
FOR I := 1 TO ParamCount DO
ParmArray[I] := ParamStr(I);
Now you have the parameters safely in ParmArray and can examine and use them
at your leisure.
A t the heart of structured programming is that old saw about the artful hiding
of details. You want to be able to focus in on the level of detail where you’re
currently working, and not be excessively concerned with either the details down
lower, or the larger view from above. On a purely structural level, the best way
of hiding details is to divide a program into subprograms (that is, procedures and
functions) and by grouping data into data structures like arrays, records, and
(once you’ve had some experience) objects. When you need to think of the task
that a subprogram does, you simply think of it as a little black box that does one
or two well-defined things. You don’t worry about what’s inside the box—unless
you’re actually going to tinker with what’s inside the box. That’s when you open
the box and take a look.
This sounds simple enough on the surface, but there are some subtle issues
surrounding it that I found very confusing when I was first learning the Pascal
language, back in the late 1970’s. There’s not a lot to discuss (which is why this
chapter is so short) but what there is happens to be extremely important, especially
if you expect to become a truly world-class programmer.
305
306 FreePascal from Square One, Volume 1
VAR
I,J,K,L : Integer;
Spread : Integer;
VAR
T : KeyRec;
BEGIN
T := RR;
RR := SS;
SS := T
END;
BEGIN
Spread := Recs DIV 2; { First Spread is half record count }
WHILE Spread > 0 DO { Do until Spread goes to zero: }
BEGIN
FOR I := Spread + 1 TO Recs DO
BEGIN
J := I - Spread;
WHILE J > 0 DO
BEGIN { Test & swap across the array }
L := J + Spread;
IF SortBuf[J].KeyData <= SortBuf[L].KeyData THEN
J := 0 ELSE
KeySwap(SortBuf[J],SortBuf[L]);
J := J - Spread
END
END;
Spread := Spread DIV 2 { Halve Spread for next pass }
END
END;
The ShellSort procedure defines five of its own variables, and has a subprogram
of its own, KeySwap. The KeySwap procedure, moreover, defines its own variable,
T. The KeySwap procedure is only called from one place inside ShellSort. Its
whole job is to hide the details of swapping two keys so that those details don’t
get in the way while you’re reading ShellSort. At the time the swap happens,
precisely how the swap happens is unimportant. You simply need to know that
the two parameters are exchanged.The ShellSort procedure itself exists to hide the
Locality and Scope 307
details of sorting an array of keys. When you’re writing a data manager program
that keeps a data file and a sorted key file, you don’t necessarily want to be bothered
with the details of how the sort happens. That’s why you only need to see this much
of ShellSort when you actually want to use it to perform a sort:
ShellSort(MySortBuf,KeyCount);
All you need to know at this point is that you’re going to sort the key array MySortBuf,
which contains KeyCount key records. The details of how the sort happens are
irrelevant. Later on, if you want to tinker with the sort routine a little bit, you can go
to the source code file that contains the ShellSort procedure and work on it. But
when you’re simply building a sort call into a program you’re writing, the call itself
is all you need.
Scope
This “visibility” property of a Pascal identifier is called its scope. The scope of an
identifier is that area of the program from which the identifier can be referenced. As
we saw in the last section, the scope of T is limited to the KeySwap procedure alone.
The scope of the KeySwap procedure is limited to ShellSort. The main program
cannot directly call KeySwap. (Nor could some other procedure, like QuickSort,
call KeySwap.) Only ShellSort can call KeySwap.
In general (and more technical) terms, the scope of an identifier is limited to the
block in which it is defined, and to all blocks defined inside that block.
What this means is that scope extends “down” the nesting hierarchy, but not “up.”
That is, the scope of variable Spread in ShellSort extends down into KeySwap,
but the scope of T in KeySwap does not extend up into ShellSort. If it needed to,
KeySwap could read the current value in Spread, but nothing in ShellSort could
read the current value of T.
PROGRAM Hollow;
VAR
Z : Integer;
Ch, Q : Char;
Gonk : String[80];
PROCEDURE LITTLE1;
VAR
Z : Integer;
BEGIN
END;
Locality and Scope 309
PROCEDURE LITTLE2;
VAR
Z : Integer;
Q : Char;
BEGIN
END;
Why?
This can be a lot of abstract logic and rules to swallow without some firm peg in the
real world to hang it on. I don’t know about you, but understanding the physical
reality of a program helps me understand its more abstract logic. So if you’re feeling
ambitious, I’ll spend a few words explaining why Pascal does things this way.
Identifiers defined within a subprogram are not visible from outside that
subprogram because until that subprogram is called, its identifiers literally do not
physically exist. We who see the whole program and all of its subprograms laid out on
the screen or on sheets of paper, all at once, sometimes forget the time-sequential
nature of a Pascal program.
Global variables are allocated in an area of memory called the data segment. They
are brought into existence when the program is loaded into memory, and remain in
existence until the program hands control back to the operating system, or to the
IDE. (The IDE can sometimes play games with a program such that the program
and the IDE can bounce back and forth during the debugging process, and the IDE
can “see” variables within the program. This is a separate issue, and involves some
magic you won’t be using in ordinary Pascal programming.) Local variables, by
contrast, do not exist until the very moment that their subprograms are called.
When a subprogram is called, it receives a little slice of an area of memory called
the stack. Inside that little slice of the stack are allocated its local variables. Now,
the stack is strictly temporary, reusable storage, and when a subprogram finishes
executing and returns control to its caller, the region of the stack that it had used is
freed up for some other subprogram to use. A subprogram’s local variables exist on the
stack only during the time that the subprogram is executing. When the subprogram returns,
its variables go poof! and are no longer anywhere to be seen.
We say that subprograms have a limited lifetime—the time that they are executing
and own their little slice of the stack containing their local variables. Before and
after that lifetime, a subprogram’s local variables simply do not exist.
So Pascal’s scoping rules are not simply the compiler being authoritarian. It can’t
allow you to reference something that doesn’t exist yet or no longer exists. Rules are
for reasons! Don’t gripe about the rules. Strive to know the reasons.
312 FreePascal from Square One, Volume 1
Chapter 14.
FreePascal File I/O
A computer program is a universe unto itself. Within its bounds data structures
are created, changed, and deleted. Calculations are performed and the results
used in still more calculations. Lots goes on in a computer program’s universe in the
cause of getting a job done.
But it’s out here in our universe where the job comes from and the finished work
is needed. Somehow, some of the things living in a computer program have to cross
the border between the program-universe and the real universe. The pathways
between the computer program and the outside world are collectively called “input/
output” or, more tersely, “I/O.” In Standard Pascal, all I/O is handled under the
umbrella category of files.
In this chapter I will explain “classic” Pascal file I/O, which (mostly) goes back
to the dawn of Pascal time. I call it classic because newer file mechanisms were
introduced with object-oriented programming (OOP), most of them in the form of
streams. I don’t have room to cover OOP in this book, so I can’t cover streams yet. In
my next book, which will cover OOP, software components, and GUI app creation
in Lazarus via FreePascal, I’ll take up the topic at length. Classic file I/O is a good way
to learn about the concept of files, and might still be useful in small utilities.
313
314 FreePascal from Square One, Volume 1
sent inward to the waiting program. Your CRT screen is a file: a flat field painted
with visible symbols by a stream of characters sent from the program inside the
computer. Your printer is a file: a device that accepts a stream of characters from the
program and places them somehow onto a piece of paper.
The last example contains an important word: “device.” Your keyboard is a
device, as is your CRT screen, as is your printer, as is your modem. All are files that
have one endpoint in the computer program and another endpoint in a physical
gadget that absorbs or emits data. (Sometimes a file may do both.) This kind of file
we call a “device file.”
The other kind of file is the more familiar kind: a collection of data recorded as
1’s and 0’s on rotating magnetic disks or (increasingly) Flash memory devices like
thumb drives, SD cards, and solid-state disks (SSDs). These are all colloquially called
“disk files,” even the ones not stored on any kind of disk. Classic files in FreePascal
are of one type or another, either device files or disk files.
This tells us that StatData is a file and contains integers, period. It says nothing
about whether or not the file exists on a disk (or on some other type of storage, like
an SSD or thumb drive), which disk, or how much data exists in the file.
That information falls under the realm of physical files. A physical file is an actual
device (for device files) or an actual collection of data on some sort of physical
storage medium. A physical disk file has a file name, a size, a record length, an
access mode, (read only, read/write, hidden, system, etc.) perhaps a timestamp for
last access and an interleave factor, and other things as well. It is not an abstraction,
and by not being an abstraction a physical file must pay attention to all those gritty
little details that make storing and retrieving data on disk possible.
All these little details are very much hardware- and operating-system dependent.
316 FreePascal from Square One, Volume 1
If Pascal had to take care of such details, it would be nearly an entirely separate
language for each computer/operating system combo it ran on, as was very nearly
the case with BASIC in its early days. So Pascal provides logical files, and the
operating system provides physical files. When a logical file becomes associated
with (or assigned to, as is often said) a physical file, a path is created between the
program’s inner universe and the outside world. That file is then said to be open.
<type> may be any valid data type except a file type. (A file of files makes no logical
sense and is illegal.) <Type> can be a standard type like Integer or Boolean, or it
can be a type that you have defined, like records, sets, enumerated types or arrays.
A file can be a file of only one type, however; you cannot declare a file of Integer
and then write records or sets to it.
Declaring a file also creates a file buffer in your data area. This file buffer is a
variable of the type that the file is declared to be. It is a “window” into the file; the
actual open end of the data pipe between the program and the outside world.
Assign creates the File Information Block (usually called a FIB) which each file
must have to be used by the program. Once associated with a physical file, a logical
file may be opened for write access or read access. This is done with Reset and
Rewrite.
An open file has in its FIB what we call a file pointer. This is not a Pascal pointer
variable (I will not be covering pointers in this book) but rather a logical marker
indicating which element in the file will be read or written to next.
Reset opens a file for reading. Some device files allow both read access and write
access at the same time; these files are opened with Reset also.
Reset does not disturb the previous contents of a disk file. The file pointer is
positioned at the first data item in the file. If the file is empty when Reset is executed,
the Boolean function EOF (end of file) returns True for that file.
Some examples:
Assign(MyFile,’C:ADDRESS.TXT’);
Reset(MyFile);
Assign(ConsoleInput,’CON’);
Reset(ConsoleInput);
Reset can also be used on a file that is already open. For an open disk file, this
will reposition the file pointer to the first record in the file. Performing a Reset on
an open device file does nothing. If the device file is one of FreePascal’s predefined
logical devices, Reset will generate an error.
Rewrite opens files that are only to be written to, and not read from while they
are currently open. When Rewrite is executed on a disk file, all previous contents
of the disk file are overwritten and lost. Note that you may use either Reset or
Rewrite on device files; the action on the device file is identical for both.
In FreePascal, Input and Output are the default files for use with Read, Write,
Readln, and Writeln. If you omit the name of a file in one of those statements, the
318 FreePascal from Square One, Volume 1
compiler assumes Input for Read and Readln, and Output for Write and Writeln.
You need only write:
Writeln(‘Files do it sequentially...’);
and FreePascal will know to send the quoted text line to your CRT screen.
Similarly, to accept input from the Input logical file, which is always connected
to your keyboard and always open, you need only write:
Readln(AString);
and the program will wait while you type text at the keyboard, continuing on only
when it detects that the Enter key was pressed, or when you have filled the string
variable out to its maximum physical length. The text you typed will be immediately
available in the variable AString. There is no need to include the identifier Input in
the Readln statement, although you may if you wish—nothing will change.
Input and Output are considered text files, which means that only printable
ASCII characters and a few control characters may be read from them or written to
them. However, Read, Readln, Write, and Writeln do contain some limited abilities
to convert binary data types like Integer to their printable representations. So it is
in fact legal to Write an integer to Output because Write converts the integer to
printable ASCII characters before Output ever sees it.
contain any 8-bit character pattern as valid data. Therefore the operating system
(and thus FreePascal) using a binary file could not tell an EOF marker character from
just more legal binary data.
There are historical (and now ancient) reasons for this: Text files created under
the DOS operating system traditionally used Ctrl-Z (hex 1A) as the EOF marker.
This was a holdover from DOS’s predecessor CP/M-80, which did not keep track of
the size of files down to the byte, as DOS did. CP/M-80 only knew how many 128-
byte blocks were contained in a file; to mark the actual end of data within the last
data block, an application had to place a Ctrl-Z character after the last true byte of
data in the file.
Although many older compilers do, FreePascal does not write a Ctrl-Z after the
last data byte in a text file. Furthermore, when reading a text file, it reports EOF at
either the true EOF reported by the operating system or at the first Ctrl-Z character
found in the file. Although FreePascal does not end a text file with Ctrl-Z, you
may at some point have to read a text file into a FreePascal program that was not
created using FreePascal! This could be important if you allow a Ctrl-Z character
to be written somewhere in the middle of a text file, perhaps by way of the Write
procedure as used for binary data like records, sets, integers, etc. If you later read
that file as text through Read or Readln, you will be able to read only up to the first
Ctrl-Z, at which point EOF will return True.
Text files in FreePascal are declared this way:
VAR
MyFile : FILE OF Text; { Both forms are legal }
YourFile : Text;
Any file that is not declared to be a FILE OF Text or simply Text is considered a
binary file. The difference between the two becomes critical when you need to detect
where data ends in the file. We’ll examine this problem in detail in connection with
the EOF function in Section 14.5
FreePascal does not implement Get and Put, nor does it make the file window
directly available to the programmer. I mention them here only because you may
run into them reading ancient Pascal code.
VAR
StatRec,Rec1,Rec2,Rec3 : NameRec;
MyFile : FILE OF NameRec;
Assign(MyFile,’B:NAMES.DAT’);
Reset(MyFile);
Read(MyFile,StatRec);
Read(MyFile,Rec1,Rec2,Rec3);
Here, NameRec is a record type defined earlier in the program. In this example,
the Read statement reads the first record from MyFile into the record variable
StatRec. There is no mention of the file window variable, although the file window
is involved beneath the surface in the code that handles the Read function. The
second Read statement reads the next three elements of MyFile into Rec1, Rec2,
and Rec3, all at one time. This is a convenient shorthand; there is no difference in
doing this than in executing three distinct Read statements.
Write works just the same way, with the same parameters; the only difference
being the direction the data is flowing:
Assign(MyFile,’B:NAMES.DAT’);
Rewrite(MyFile);
Write(MyFile,StatRec);
Write(MyFile,Rec1,Rec2,Rec3);
Here, the file MyFile is opened and cleared with Assign and Rewrite. Then the
four defined records are written to the file.
Read and Write work sequentially; each time Read or Write is used, the file
Classic FreePascal File I/O 321
pointer is bumped to the next element down the file. The process never works
backwards; you cannot begin at the end of the file and work your way back. For
that sort of thing you need random file I/O, which I cover briefly at the end of this
chapter.
VAR
Unit : Char:
Count : Integer;
What is happening here is that a list of four data items is being sent to device file
Output. Two are string literals, one is a character, and one is an integer.
Text files, as we saw in the previous section, may contain only printable ASCII
characters and certain control characters. Integer variables are not ASCII characters;
rather, they are two-byte binary numbers that are not necessarily printable to the
screen.
Yet when you write
VAR
I : Integer;
I := 42;
Write(I);
the two ASCII characters “4” and “2” appear on your screen. They are an ASCII
representation of a two-byte binary integer that could as well have been written in
base 2 as 00000000 00101010. Read and Write contain the machinery for converting
between numeric variables (which are stored in binary form) and printable ASCII
numerals.
Write also has the ability to take Boolean values (which are actually binary
numbers 0 or 1) and convert them to the words “True” and “False” before passing
322 FreePascal from Square One, Volume 1
them to a text file. Read, however, does not convert the ASCII strings “TRUE” or
“FALSE” to Boolean values!
Read and Write when used with text files can accept data types Integer, Char,
Byte, Word, LongInt, SmallInt, ShortInt, Cardinal, Currency, QWord and
subranges of those types; type ShortString and derived string types; plus type Real,
Single, Double, Extended, and Int64. (Comp, while it does work with Write, is
now considered obsolete and may not reliably work in all FreePascal constructs.)
Write will also accept Boolean values; remember that Read will not. Enumerated
types, pointers, sets, arrays, and record types will generate errors during compilation
if you attempt to use them with text files.
A quick note about type Currency: Although the type was created to handle
decimal money values, to show a Currency value as dollars and cents (or some
other decimal currency), you must use write parameters, with the width after the
decimal set to 2:
Bux : Currency;
Bux := 337.95
Write(Bux : 5 : 2);
As best I know, FreePascal has no feature to automatically place a dollar sign ($)
in front of a displayed Currency value.
Although Read will accept a string variable, it’s better practice to use Readln
for reading strings. (See below on Readln.) The problem is that strings on a text file
character stream carry no information about how long they are. If you Read from a
text file stream into a string variable, the string variable will accept characters from
the file until it is physically filled. If the file ends before the string is completely filled,
the system may hang or fill the remainder of the string with garbage.
line character (EOL) that is inserted into the character stream of a text file after each
line of printable characters. The definition of the EOL character may vary among
operating systems. For most microcomputers EOL is not one character but two: the
sequence carriage return/line feed ($0D/$0A). This is a holdover from Teletype’s
heyday when it took one control character to return the typehead to the left margin,
and yet another to index the paper up one line.
The Writeln procedure is exactly like Write, save that it follows the last item
sent to the text file character stream with the EOL character. For our discussion EOL
will always be the pair CR/LF.
PROGRAM WriteInt1;
VAR
IntText : Text;
I : Integer;
BEGIN
{ Create ‘D:\Temp\’ or change it to a valid drive or }
{ path on your system: }
Assign(IntText,’D:\Temp\INTEGERS.TXT’);
Rewrite(IntText);
FOR I := 1 TO 25 DO Writeln(IntText,I);
Close(IntText);
END.
This program WriteInt1 writes the ASCII equivalent of the numbers from 1-
25 to a text file named INTEGERS.TXT. Each numeral is followed by a CR/LF pair.
A hexdump of file INTEGERS.TXT will allow you to inspect the file character
stream:
0000 31 0D 0A 32 0D 0A 33 0D 0A 34 0D 0A 35 0D 0A 36
0010 0D 0A 37 0D 0A 38 0D 0A 39 0D 0A 31 30 0D 0A 31
0020 31 0D 0A 31 32 0D 0A 31 33 0D 0A 31 34 0D 0A 31
0030 35 0D 0A 31 36 0D 0A 31 37 0D 0A 31 38 0D 0A 31
0040 39 0D 0A 32 30 0D 0A 32 31 0D 0A 32 32 0D 0A 32
0050 33 0D 0A 32 34 0D 0A 32 35 0D 0A
If you know your ASCII well (or have a table handy on paper or online) you can see
the structure of the character stream in this file: ASCII numerals separated by $0D/$0A
pairs. Because Windows knows to the byte (here, 91) how large the file is, it doesn’t
need to tack a Ctrl-Z on the end of the file. Older Pascals running on older operating
systems may fill out the remainder of the file’s 128-byte block with Ctrl-Zs.
324 FreePascal from Square One, Volume 1
In a sense, Writeln writes only strings (minus length bytes) to its files. If you
hand it a variable that is not a string, Writeln will (if the conversion is possible)
convert it to its string equivalent before writing it to its file.
Readln reads one line from a text file. A line in a text file, again, is a series of
characters up to the next EOL marker. If Readln is reading into a string variable, the
number of characters read becomes the logical length of the string. So unlike Read
and Write, Readln and Writeln can in fact maintain information on string length
in a file, since all strings written by Writeln are terminated by EOL markers.
When written, data (if any) in the variable will be right-justified within a field of
spaces <field width> wide. For example:
CONST
Bar = ‘|’;
VAR
I : Integer;
R : Real;
CH : Char;
OK : Boolean;
Txt : String;
I := 727;
CH := ‘Z’;
R := 2577543.67;
OK := False;
Txt := ‘Grimble’;
Writeln(Bar,I:5,Bar);
Writeln(Bar,CH:2,Bar);
Writeln(Bar,R:12,Bar);
Writeln(Bar,OK:7,Bar);
Writeln(Bar,TXT:10,Bar);
Writeln(Bar,-R,Bar);
Writeln(Bar,R,Bar);
Classic FreePascal File I/O 325
| 727|
| Z|
| 2.5775E+006|
| FALSE|
| Grimble|
|-2.5775436699999999E+006|
| 2.5775436699999999E+006|
Note that the real numbers are always expressed in exponential (also called
“scientific”) notation, that is, as powers of ten, even though originally expressed with
a decimal point and no exponent. To express a real number without the exponent,
you must include a second write parameter for the width of the decimal part of the
field:
<real value> : <field width> : <decimal width>
The value <decimal width> indicates how many decimal places are to be displayed.
For example:
R := 7.775;
S := 0.123456789;
T := 7765;
Writeln(Bar,R:10:3,Bar);
Writeln(Bar,R:10:1,Bar);
Writeln(Bar,R:5:2,Bar);
Writeln(Bar,S:6:6,Bar);
Writeln(Bar,S:12:6,Bar);
Writeln(Bar,S:12:12,Bar);
Writeln(Bar,S:5,Bar);
Writeln(Bar,T:5:2,Bar);
Writeln(Bar,T:5,Bar);
Writeln(Bar,T:6:6,Bar);
| 7.775|
| 7.8|
| 7.78|
|0.123457|
| 0.123457|
|0.123456789000|
| 1.2E-001|
326 FreePascal from Square One, Volume 1
|7765.00|
| 7.8E+003|
|7765.000000|
A reminder here: You cannot begin a fractional real number (such as .123456789)
with a decimal point. You must begin the number with a 0 as shown, or FreePascal
will issue an error during compilation.
https://wiki.freepascal.org/Using_the_printer
IOResult
Working with creatures like disk files that lie outside the borders of the program
itself is risky business. You can build machinery into your program to make sure
that the program never attempts to divide by zero, or never attempts to index past
the bounds of an array. But how do you make sure that (when you want to open a
disk file) the file is on the disk and the disk is in the proper disk drive?
The program does not have absolute control over disk files in the way it has
absolute control over numbers, arrays, and other variables. The only way to be sure
a file is on the disk is to go out and try to read it. If the file isn’t out there, you need
some way to recover gracefully.
The runtime code that FreePascal adds to every program it compiles guards
against runtime errors such as an attempt to open (for reading) a file that does not
exist. If such an error is detected,your program will terminate.
Obviously, if there is a legitimate possibility that a file does not exist on the disk,
you cannot allow your program to just crash. Better to determine that an error has
happened without crashing, so that the program could do something about it, like
create a new file or look somewhere else for the old one. FreePascal provides the
Classic FreePascal File I/O 327
IOResult function to let the program know how successful it has been in striking
a path to the outside world. It is predeclared by FreePascal this way:
FUNCTION IOResult : Integer
After each I/O statement is executed, a value is given to the IOResult function.
This value can then be tested to determine whether the I/O statement completed
successfully. A 0 value indicates that the I/O operation went normally; anything
else constitutes an error code.
However, the runtime code’s error traps will still crash your program when
an error is encountered, whether or not you use IOResult. To keep the program
running in spite of the error, you must disable the error trap with the compiler
directive $I. This is done by surrounding the I/O statement with an {$I-} directive
(turn traps off) and an {$I+} directive (turn traps on again.)
For example:
Assign(MyFile,’D:BOWLING.DAT’);
{$I-} Reset(MyFile); {$I+} { Suspend error traps during Reset }
IF IOResult <> 0 THEN
BEGIN
Beep;
Writeln(‘>>The bowling scores file cannot be opened.’);
Writeln(‘ Make sure the scores file is on the D: drive’);
Writeln(‘ and press (CR) again:’)
END;
Given that restriction, if you want to see the actual value returned by IOResult,
you must assign the value of IOResult to an integer variable immediately after an
I/O statement and work with the integer variable rather than the IOResult function
itself. Remember: IOResult is a function, not a variable!
Another example of IOResult in use lies in the Averager program in the next
section. Averager opens a binary file of integers, reading and displaying them one-
by-one until it reaches the end of the file, and then calculates and displays the average
of the integers it has read. If it cannot open the file of integers it recovers and issues
an error message with the help of IOResult.
EOF
Knowing where a disk file ends is critical. A built-in function called EOF (End Of
File) provides this service to your programs. EOF is predeclared this way:
FUNCTION EOF(FileVar : <any file type>) : Boolean
FileVar can be any legal file type. The EOF function returns True as soon as the
last item in the file has been read. At that point the file pointer points just past the
end of data in the file, and no further reads should be attempted. A runtime error
will occur if you try to read beyond the end of a file. This applies to both text files
and binary files.
FreePascal’s runtime code determines EOF by using information stored by the
operating system for each disk file. For text files it keeps a count of characters read
from the file and compares that to the size of the file at each read; when bytes read
equals or exceeds the number of bytes the OS says are in the file, EOF returns True.
For binary files, FreePascal knows the size of each record read (since every file is a
FILE OF <something>, where <something> is a declared data type with a fixed
size) and compares a similar count of records read against the file size figure.
Text file EOF is also triggered by the presence of a Ctrl-Z character ($1A) in the
file. Any modern OS knows exactly how many bytes are in every file at all times, so
with text files, FreePascal reports EOF either when the OS indicates the last character
had been read, or when a Ctrl-Z is encountered in the text stream from the file.
The following program assumes a binary file of 16-bit small integers (type
SmallInt) named INTEGERS.BIN. (I provide such a file on the listings archive for
this book, or if you’re ambitious you can write a short program that generates a file
for you. Try it!) Averager reads and displays all the integers in the file, testing EOF
at each read. As it reads each integer it keeps a running total and a running count,
and then produces an average value for all the integers in the file. Note the use of the
$I- and $I+ compiler directives when opening the file:
Classic FreePascal File I/O 329
{--------------------- ----------------------------------------}
{ Averager }
{ Binary file I/O demonstration program }
{ by Jeff Duntemann }
{ FreePascal 3.2.2 }
{ Last update 5/3/2025 }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
PROGRAM Averager;
VAR
IntFile : FILE OF SmallInt;
I,J,Count : SmallInt;
Average,Total : Real;
BEGIN
Assign(IntFile,’INTEGERS.BIN’); { Open the file of 16-bit integers }
{$I-} Reset(IntFile); {$I+} { Traps off, Reset, then on again }
I := IOResult;
IF I <> 0 THEN { If IOResult isn’t 0, can’t open it }
BEGIN
Writeln(‘>>File INTEGERS.BIN is missing or damaged.’);
Writeln(‘ Please investigate and run the program again.’);
Write(‘ Press Enter:’);
Readln;
END
ELSE
BEGIN
Count := 0; Total := 0.0; {Initialize variables}
WHILE NOT EOF(IntFile) DO {Go through the file }
BEGIN
Read(IntFile,J); {Read a number from the file }
IF NOT EOF(IntFile) THEN {If EOF is not set, keep going }
BEGIN {Display integer & add to total }
Writeln(J:6);
Count := Count + 1;
Total := Total + J
END;
END;
Close(IntFile); { When it hits EOF, close the file }
Average := Total / Count; { Calculate average }
Writeln; { Show count & average values: }
Writeln(‘>>There are ‘,Count,’ integers in INTEGERS.BIN.’);
Writeln(‘ Their average value is ‘,Average:10:4,’.’);
Write(‘ Press Enter:’); { Wait for user to press Enter }
Readln;
END
END.
330 FreePascal from Square One, Volume 1
The function FileSize returns an Int64 count of the number of items stored in
the file. FileVar is an open binary file. (Not a text file!) An empty file will return a
zero value.
Function FilePos is predeclared this way:
FUNCTION FilePos(FileVar : <binary filetype>) : Int64;
A binary file’s file pointer is a counter that indicates the next item to be read
from the file. The FilePos function returns the current value of a file’s file pointer.
FileVar is an open binary file. (Not a text file.) The first item in a file is item number
0. When a file is first opened, its file pointer is set to 0. Each Read operation will
increment the file pointer by one. The Seek procedure (more on Seek later in this
chapter) will put the file pointer to a particular value to enable random access to a
binary file.
Flush
Every file has a buffer in memory, and when you write to a file, the data you’ve written
actually goes to the buffer rather than directly to the physical disk file. Periodically,
based on decisions it makes on its own, the Turbo Pascal runtime code will flush the
buffer to disk, actually transferring the data to the physical disk file. You can force
such a flush to disk with the Flush procedure. It is predeclared this way:
PROCEDURE Flush(<filevar>);
Classic FreePascal File I/O 331
Here, <filevar> is any file variable opened for output. Flush has no effect on a file
opened for input. IOResult will return a 0 value if the file was flushed successfully.
Do not use Flush on a closed file!
Close
Closing a file that you have opened ensures that all data written to the file is physically
transferred from the file buffer to disk. In FreePascal the Close procedure does this job:
PROCEDURE Close(<filevar>);
As with Flush, <filevar> is any opened file. It is legal to use Close to close a file
that has been closed already, though it doesn’t accomplish anything useful.
And of course, if you exit a program before closing a file that has been written
to, the runtime code makes no guarantee that all records written to the file buffer
will actually make it out to the physical disk file. As with most file-related routines,
IOResult will be set to 0 if the file was closed successfully. Otherwise, IOResult
will contain a nonzero error code.
Erase
Deleting a disk file from within a program is done with the Erase procedure:
PROCEDURE Erase(<filevar>);
Again, <filevar> is any file variable that has been assigned to a physical file. In
other words, if you try to Erase a file variable to which no physical file has yet been
assigned, the runtime code has no way of knowing which file you want to delete:
VAR
NumFile : FILE OF Integer;
Assign(NumFile,’VALUES.BIN’);
Erase(NumFile);
Do not attempt to Erase an open file. Close it first, or the results could be
unpredictable. IOResult will return 0 if the file was successfully deleted.
Rename
FreePascal allows you to change the name of a disk file from within a program with
the Rename procedure:
PROCEDURE Rename(<filevar>; NewName : ShortString);
332 FreePascal from Square One, Volume 1
<filevar> is any file that has been assigned to a physical file with Assign. As
with Erase, trying to rename a file without connecting it with a physical file is
meaningless. Also like Erase, renaming an open file is a no-no. IOResult will
return a 0 if the file was successfully renamed.
Append
Append provides a way to quickly move the file pointer to the end of a text file
without having to read the file and throw away the characters read up to EOF.
Append is used instead of Rewrite:
PROCEDURE Append(<filevar>);
<filevar> must first be assigned to some physical file before Append can be
used. The contents of the file are not destroyed, as they are with Rewrite. The file is
opened for output, however, at EOF. Adding text to the file with Write or Writeln
will position the new text at and following EOF.
IOResult will return a 0 if the operation was successful. Keep in mind that
Append may be used only with text files. Attempting to use Append with a non-
text file type will trigger
Error 63: Invalid file type
Truncate
Truncate is conceptually similar to Append. Both prepare a file for the adding of
additional data via Write or (for text files) Writeln. Unlike Append, Truncate may
be used with any type of file: text, binary, or untyped. Truncate is predeclared this
way:
PROCEDURE Truncate(<filevar>);
When executed, Truncate chops a file off at the current position of the file
pointer. In other words, if you have read part way down a file and execute Truncate,
the remainder of the file will be thrown away. The file is then ready for output, even
if you opened the file with Reset.
Filter programs
There is a whole class of programs that read a file in chunks, perform some
manipulation on the chunks, and then write the transformed chunks back out to
another file. This type of program is called a “filter” program because it filters a file
through some sort of processing step. The data changes according to a set of rules
as it passes through the processing part of the program.
A good example would be a program to force all lower-case characters in a file
to upper case. FreePascal is not sensitive to character case but some programs and
language processors (generally old versions of old languages like COBOL) do not
interpret lowercase characters correctly. To pass a text file between Pascal and
vintage COBOL, all lowercase characters in the file must be set to uppercase.
A filter program to accomplish this task would work this way:
Open the input file for read and create a new output file.
While not end-of-file keep doing this:
Read a line from the input text file.
Force all lowercase characters in the line to uppercase.
Write the line out to the output text file.
Close both files.
This basic structure is the same for all text file filter programs, except for the
processing that is done. You could just as easily force all uppercase characters in
the line to lowercase, count the words in the line, remove all BEL characters (Ctrl-G)
from the line, expand HT (tab; Ctrl-I) characters to 8 space characters, and so on.
You could in fact combine two or more processes into one filter program; say,
force lowercase to uppercase and count characters.
The program Caser shown below can perform two distinct functions: It can
force all lowercase characters in a text file to uppercase, or all uppercase characters
to lowercase. (Not both at the same time, however.) Which of the two actions is
taken depends on a parameter entered on the command line:
Caser up D:COBOL.SRC Forces lower to upper
Caser incorporates a good many of the file routines we’ve been discussing in
this chapter, and understanding how it works will help you understand how those
routines interact within a program.
334 FreePascal from Square One, Volume 1
{--------------------------------------------------------------}
{ Caser }
{ }
{ An upper/lower case conversion filter program for text files }
{ }
{ by Jeff Duntemann }
{ FreePascal V3.2.2 }
{ Last update 5/4/2025 }
{ }
{ From: FreePascal From Square One by Jeff Duntemann }
{--------------------------------------------------------------}
PROGRAM Caser;
CONST
Upper = True;
Lower = False;
VAR
Quit : Boolean;
WorkFile : Text;
TempFile : Text;
NewCase : Boolean;
WorkLine : ShortString;
WorkName : ShortString;
TempName : ShortString;
CaseTag : ShortString;
{>>>>ForceCase<<<<}
CONST
Uppercase : SET OF Char = [‘A’..’Z’];
Lowercase : SET OF Char = [‘a’..’z’];
VAR
I : INTEGER;
BEGIN
IF Up THEN FOR I := 1 TO Length(Target) DO
IF Target[I] IN Lowercase THEN
Target[I] := UpCase(Target[I])
ELSE { NULL }
ELSE FOR I := 1 TO Length(Target) DO
IF Target[I] IN Uppercase THEN
Target[I] := Chr(Ord(Target[I])+32);
ForceCase := Target
Classic FreePascal File I/O 335
END;
{>>>>MakeTemp<<<<}
VAR
Point : Integer;
BEGIN
Point := Pos(‘.’,FileName);
IF Point > 0 THEN Delete(FileName,Point,(Length(FileName)-Point)+1);
TempName := Concat(FileName,’.$$$’)
END;
BEGIN
Quit := False;
IF ParamCount < 2 THEN { Missing parms error }
BEGIN
Writeln(‘>>CASE<< V2.00 By Jeff Duntemann’);
Writeln(‘ From the book, FREEPASCAL FROM SQUARE ONE’);
Writeln(‘ Last modified 5/1/2025’);
Writeln;
Writeln(‘This program forces all characters of a text file to either
‘);
Writeln(‘upper or lower case, as requested. Characters already in ‘);
Writeln(‘the requested case are not disturbed.’);
Writeln;
Writeln(‘CALLING SYNTAX:’);
Writeln;
Writeln(‘CASE UP|DOWN <filespec>’);
Writeln;
Writeln(‘For example, to force all lowercase characters of file’);
Writeln(‘FOO.COB to uppercase, invoke CASE this way:’);
Writeln;
Writeln(‘CASE UP FOO.COB’);
Writeln;
END
ELSE
BEGIN
WorkName := ParamStr(2);
Assign(WorkFile,WorkName); { Attempt to open the file }
{$I-} Reset(WorkFile); {$I+}
IF IOResult <>0 THEN
BEGIN
Writeln(‘<<Error!>> File ‘,WorkName,’ does not exist.’);
336 FreePascal from Square One, Volume 1
Most of Caser is setup: making sure files exist; making sure valid commands
were entered, and so on. The real meat of the program is simplicity itself:
This loop executes repeatedly as long as there are lines to be read in WorkFile.
A line is read, ForceCase adjusts the case of the characters in the line, and then the
line is written to TempFile. When Readln reads the last line in the text file, the EOF
function will immediately return True. The text file is “filtered” through ForceCase
into a temporary file. When the original file has been read completely, it is erased
with Erase and the temporary file is renamed to become the original file.
If you’re nervous about deleting your original text file (and that is not a totally
unhealthy feeling) you could close it with Close instead of Erase and then give
TempFile a new file extension instead of .$$$. (I have used “.ZZZ” in the past.) If
you’re careless about backing up important files, this is a very good idea.
TYPE
KeyFile = FILE OF KeyRec;
CfgFile = FILE OF CfgRec;
IntFile = FILE OF Integer;
Only one data type may be stored in a binary file. You could not, for example,
write one variable of type KeyRec and another variable of type CfgRec to the
same logical file.
Each instance of a data item in a binary file is called a “record,” whether the data
item is actually a Pascal RECORD type or not. One integer stored in an IntFile as
defined above could be considered a record of the IntFile.
One common use of binary files puts only one record in the file: The
“configuration file.” Consider a complicated program that performs file
maintenance and telecommunications. The program is used at a great many sites
owned by a large corporation. The names of the files it works with change from
site to site, and so on
Rather than “hard-code” things like telephone numbers into the Pascal source
file, it makes more sense to store them out to a configuration file. The easiest way is
to define a record type containing fields for all the site-specific information:
Sec9:338 FreePascal from Square One, Volume 1
TYPE
CfgRec = RECORD
SiteName : String;
SiteCode : Integer;
AuthOp : String;
HostCode : Integer;
PhoneNum : String;
P1FileName : String;
P2FileName : String;
AXFileName : String
END;
VAR
SiteFile : CfgFile;
CfgData : CfgRec;
All the data items that change from site to site are present in one single record.
When loaded with the correct site values for a particular site, the record can be
easily written to disk:
Assign(SiteFile,’B:SITEDATA.CFG’);
Rewrite(SiteFile);
Write(SiteFile,CfgData);
Close(SiteFile);
Here, once SiteFile is opened, the single CfgRec with changed data is written to
disk with the Write statement.
If you know the position of a record in a file, you can move the file pointer to
that record in one operation by using Seek. The Seek procedure allows you true
random access to the file. Seek is predefined this way:
PROCEDURE Seek(FileVar : <binary filetype>, RecNum : Int64)
Attempting to seek beyond the end of a binary file will trigger a runtime error.
Testing a file’s size using the FileSize function before using Seek is always a good
idea.
W hy compile the whole thing when you only need to compile the piece
you’re working on? Time is money (even small slivers of time add up) and
compilation takes time. Why waste that time?
There’s no need to. Pascal naturally separates a program into logical chunks—at
least if you don’t fight the spirit of the language. Good program design calls for
relatively independent modules that may be compiled separately, and then linked
together in one quick, final step before testing the completed program.
This is called separate compilation. Separate compilation has never been part of the
official Pascal language definition, but time has shown that it’s very hard to manage
large projects without it. To provide for separate compilation, FreePascal and most
others (including the Borland Pascals) implement the units paradigm pioneered by
UCSD Pascal decades ago. This chapter introduces the mechanisms by which separate
compilation happens in FreePascal. It’s a surprisingly subtle business, and not all of
it can be covered in an introductory text such as this. I’ll have more to say about
separate compilation and units in future books on FreePascal and Lazarus.
L et’s say the UPS man rolls up to your door one day and drops a cardboard box
from Lieutenant Kije’s Military Surplus in your porch. You know where it came
from, but your memory of what the order contains has gotten a little fuzzy.
So you rip open the little clear plastic slap-on pocket and pull out the sheet of
paper marked “packing list.” Right in a row is a summary of what’s in the box: 2 spur
gears, 96 tooth, brass. One pillow block ball bearing, 1/4”. One alarm clock, Navy
Surplus. (Original cost $900.) One tank prism; tank not included.
Without actually ripping open the box, you then know what’s in it. If, for example,
the packing list had read something like, “25 polyethelene shower curtains, mauve,” I
would start to suspect that the UPS man had dropped the wrong box on my porch.
341
342 FreePascal from Square One, Volume 1
A Pascal unit is like a little like a sealed box with a packing list. Each unit has two
primary parts:
1) an interface part; and
2) an implementation part.
The interface part is a lot like a packing list. It is an orderly description of what is in
the unit, without the actual code details of the functions and procedures within the
unit. The interface part may include constant, type, and variable definitions, along
with the parameter line portions of the functions and procedures within the unit.
This last item may seem a little strange. Consider the following line of code:
PROCEDURE FogCheck(InString : String; VAR FogFactor : Integer);
This isn’t all of the procedure, obviously—but like it or not, it’s all you really need
to see of FogCheck to be able to make use of the procedure in your own programs.
You still need to know the relationship between the input parameter InString and the
output parameter FogFactor, of course, but that’s a documentation issue. Knowing
what procedure FogCheck does is an entirely separate matter from knowing how
it does it. If you know that the foggier the input string is, the higher the value will
come back in FogFactor, well, that’s sufficient in most cases.
Where’s the rest of FogCheck? In the implementation part of the unit. In other
words, inside the sealed box. The box contains the substance of the order, the actual
goods. The packing list contains a description. That is the critical difference between
interface and implementation.
At this point the packing list metaphor begins to break down, because in order
to do anything with my brass gears and tank prism I have to rip open the box and
take the goods out. A separately-compiled unit may remain a sealed box in the sense
that you cannot read the details of what lies inside, but the interface part of the unit
allows your own programs to hook into and use the contents of the box.
PROGRAM Caveat;
USES Crt;
BEGIN
ClrScr; { In unit Crt }
GoToXY(12,10); { ditto }
Writeln(‘Better to light one single candle...’);
Writeln(‘...than to trip on a rake while changing the fuse.’);
END.
Here, the program Caveat contains a new type of Pascal statement: The USES
statement. (USES is a reserved word.)
Caveat uses a unit called Crt that is included with FreePascal. Crt contains (as
you might imagine) routines that deal with screen handling for DOS-style text
windows. ClrScr and GotoXY are the most common examples. Such routines
were never really part of the Pascal language. Both are ordinary procedures that you
could have written yourself. They originated in UCSD Pascal and have appeared in
nearly all other Pascal implementations since then.
I have to point out something important here: The same isn’t true about those
other stalwarts of simple console-mode example programs, Read, Readln, Write,
and Writeln. These are not procedures in the strictest Pascal sense. If you’ve been
through the earlier parts of this book you’ll know why: They can take a variable
number of parameters of many different types in any order at all, which is a gross
violation of Pascal’s rules and regulations regarding procedures. This being the
case, they must be “special cases” built into the compiler, because the compiler
has to generate different object code to perform each separate call to Read or
Write depending on the number and types of the parameters used. So while many
beginners think of Writeln as a CRT-oriented procedure, it does not “live” in the
same unit with all the other CRT-oriented functions and procedures supplied with
FreePascal.
The USES statement can take (almost) any number of unit names, separated
by commas. (FreePascal programs are limited to 1,024 units.) The order you place
them in the USES statement is not important unless code inside one unit references
declarations made inside one of the other units. In that case, in keeping with Pascal’s
dictum of “define it before you reference it” the called identifier must be named before
the caller. Here’s a very simple example of how you can go wrong:
344 FreePascal from Square One, Volume 1
USES Crt,DiceUnit,BoxUnit;
BEGIN
END.
The short code snippet above is the top-level structure of a simple dice game.
DiceUnit draws dice. BoxUnit draws boxes. Drawing dice requires drawing boxes.
The game draws a box, and then puts some number of “o” characters on the box to
draw the faces of typical 6-sided dice.
Clearly, the game has to draw a box before putting the spots on the box. DiceUnit
calls procedures inside BoxUnit. This means that FreePascal has to encounter
BoxUnit before it encounters DiceUnit—and the Crt unit before it encounters
either. (The procedure GotoXY lives in the Crt unit, and both of the other units call
GotoXY.) Rearranging the order in which the units appear in the USES statement
fixes the problem:
PROGRAM DiceGame; { This will compile! }
USES Crt,BoxUnit,DiceUnit;
BEGIN
END.
3. It will look in the directory where the FreePascal compiler’s executable file
is. This is not a good strategy for beginners!
4. It will look in all directories in the unit search path.
Unfortunately, you can’t place a drive specifier or a path specifier in a USES
clause. In other words, these are not legal USES clauses:
USES Crt,DOS,C:RingBuf; { Won’t compile! }
Unit locations are set on a per-project basis. Each project can have a file path of its
own to direct the compiler to the units that the project USES. This can be set from
the Project|Project Options|Compiler Options|Paths. However, remember that
when you create and save a new project, Lazarus will keep the directory in which
the new project was saved, and look there first for unit files.
A program could use both units as shown above and no error message would be
generated. But—which ClrScr would be actually incorporated into the program?
With no more information than the procedure name to go on, FreePascal will
link the last procedure named ClrScr that it finds in scanning the units named in the
USES statement. In the example above, it would scan CustomCrt after scanning
Crt, and thus it would link the custom-written ClrScr routine into the program.
You could exchange the unit names Crt and CustomCrt in the USES statement,
and the compiler would then link the standard ClrScr routine into your program:
USES DOS,CustomCrt,Crt;
However, if you arrange the USES statement like this, nothing in CustomCrt can
use any of the many useful routines in Crt. This may not always be an issue, but it
does limit your options.
There is a better way. You can specify the name of the unit that contains an
identifier when you use the identifier. The notation should be familiar to you from
working with Pascal record types, and works in a very similar fashion:
346 FreePascal from Square One, Volume 1
PROGRAM WeirdTextStuff;
USES Crt,CustomCrt;
BEGIN
Crt.ClrScr; { Clears the visible PC text screen }
ClrScr; { Clears the other text screens too }
. . .
END.
This is often called dotting. In the same way that you can have two different record
types with identical field names, you can have two or more units containing
identical identifiers, and there will be no conflict. You simply choose the one you
want by prefixing it with the unit name and a period character at each invocation.
The default identifier in cases where no unit name precedes the reference is the first
one found in scanning the units in the USES statement.
Note that the resemblance to record references ends there. FreePascal has no
WITH statement feature for specifying unit names.
UNIT Skeleton;
INTERFACE
IMPLEMENTATION
END.
There are some immediate departures from the expected here. The reserved words
INTERFACE and IMPLEMENTATION are not statements, and therefore are not
followed by semicolons. Like the reserved words BEGIN and END, they serve to set
off groups of statements that belong together. Also, there is an END but no BEGIN.
Units do not have program bodies.
Fleshing out our unit a little bit will bring out the differences between the interface
and implementation parts:
Units and Separate Compilation 347
UNIT Skeleton;
INTERFACE
TYPE
MyType = ItsDefinition;
VAR
MyVar : MyType;
IMPLEMENTATION
VAR
PrivateVar : MyType;
BEGIN
END;
BEGIN
END;
END.
Note here that the INTERFACE reserved word must come before the USES
statement, if any. Nothing, in fact, may come between the unit name and the
INTERFACE reserved word.
One type and a variable of that type are defined in the interface section shown.
Both of these definitions are “visible” to any program or unit that uses Skeleton. A
function and a procedure are also defined in the interface part of Skeleton, and like
MyType and MyVar are visible to any unit or program that uses Skeleton.
348 FreePascal from Square One, Volume 1
Now look down to the implementation section of the unit, where the bodies of
the function and procedure are given. Notice that a variable name PrivateVar is
declared in the implementation section. As its name implies, PrivateVar is known
only within the implementation section of the unit. No other program or unit can
reference the identifier PrivateVar or in any other way know that it exists or how it
is defined.
PrivateVar can, however, be accessed by any of the procedures or functions
defined within the unit. So, in a sense, the effects of PrivateVar can be “felt” by
outside programs or units that use the subprograms that have access to PrivateVar,
but the variable itself remains invisible to anything outside the unit in which it is
declared.
Why is this important? Like so many of Pascal’s structural limitations, it is done
to minimize the possibility of undeclared “sneak paths” occurring between routines
when those paths are not desired. Such sneak paths make possible bugs of a truly
insidious nature.
{--------------------------------------------------------------}
{ BoxStuff }
{ }
{ Unit to demonstrate separate compilation -- draws text boxes }
{ }
{ by Jeff Duntemann }
{ FreePascal 3.0.4 }
{ Last update 11/15/2019 }
{ }
{ From: FREEPASCAL FROM SQUARE ONE by Jeff Duntemann }
{--------------------------------------------------------------}
UNIT BOXSTUFF;
INTERFACE
TYPE
LineRec = RECORD
ULCorner,
URCorner,
LLCorner,
LRCorner,
HBar,
VBar,
LineCross,
TDown,
TUp,
TRight,
TLeft : String[4]
END;
{ PCLineChars: }
{ Contains box-drawing strings for MakeBox.}
{ Any program or unit that USES BoxStuff }
{ can access the PCLineChars constant just }
{ as though it had been defined within the }
{ USEing program or unit. }
CONST
PCLineChars : LineRec =
(ULCorner : #201;
URCorner : #187;
LLCorner : #200;
LRCorner : #188;
HBar : #205;
VBar : #186;
LineCross: #206;
TDown : #203;
TUp : #202;
TRight : #185;
TLeft : #204);
IMPLEMENTATION
{ <<<<MakeBox>>>> }
VAR
I : Integer;
BEGIN
IF X < 0 THEN X := (80-Width) DIV 2; { Negative X centers box }
WITH LineChars DO
BEGIN { Draw top line }
GotoXY(X,Y); Write(ULCorner);
FOR I := 3 TO Width DO Write(HBar);
Write(URCorner);
{ Draw bottom line }
GotoXY(X,(Y+Height)-1); Write(LLCorner);
FOR I := 3 TO Width DO Write(HBar);
Write(LRCorner);
{ Draw sides }
FOR I := 1 TO Height-2 DO
BEGIN
GotoXY(X,Y+I); Write(VBar);
GotoXY((X+Width)-1,Y+I); Write(VBar)
END
END
END;
END.
UNIT Skeleton;
INTERFACE
IMPLEMENTATION
INITIALIZATION
BEGIN
END;
FINALIZATION
BEGIN
END
END.
One thing that may look odd is that the two words INITIALIZATION and
FINALIZATION are in uppercase. In this book at least (and in my tutorials
generally) reserved words are in all caps, and the two are reserved words in nearly
all implementations of Object Pascal, including Delphi. Neither is required for a
unit to compile. As you’ll notice, the example unit BoxStuff presented earlier has
neither.
The Skeleton unit as shown above will compile, even though it’s empty and
generates no code.
When the program that contains this statement is run, the initialization section
for Crt executes first, followed by the initialization section for Mouse. There is no
initialization section for the unit BoxStuff, so as soon as the initialization section
of Mouse finishes executing, the main program body begins executing.
The finalization sections of the units used in a program will execute in reverse
order after execution of the program itself terminates. For example, in the USES
statement above, the units will be initialized in the order Crt,Mouse,BoxStuff.
When the program ends, the finalization sections of the three units will be executed
in the order BoxStuff,Mouse,Crt.
352 FreePascal from Square One, Volume 1
Of what use are initialization sections and finalization sections? There are
several, although most of them are relatively advanced concepts that you may not
need to use until you have come up to speed in Pascal programming in general and
(especially) begin using objects.
Most simply, an initialization section can initialize global variables declared in the
unit to some desired initial value. This relieves the program itself of the responsibility,
and avoids the possibility that the programmer will forget to add initialization
code to the beginning of his program that uses the unit. Also, in situations where a
vendor sells a unit as a separate product, the initialization section guarantees that
any globals that need to be initialized will be initialized so that the unit (which may
not be sold with source, and hence not fully understood by the programmer who
works with it) will work correctly without depending on the programmer. Also, a
unit may need to allocate memory on the heap for data structures.
Finalization sections are used less often. Mostly they release memory that was
allocated by code somewhere in the unit. Again, this will make more sense (and
become more useful) once you begin using objects, pointers, and the heap. Alas, I
cannot cover those topics in this one introductory volume.
Units and Separate Compilation 353
354 FreePascal from Square One, Volume 1
What’s Next?
M any people who have downloaded this PDF since 2011 have written to ask
me how much it will cover once I consider it complete—and what my next
project involving FreePascal/Lazarus will be. Well, with the 5/5/2025 revision, I
consider this book complete. Let’s talk about that a little.
First of all, remember what I’m trying to do with this book: Create an introduction
to not only FreePascal but to the ideas of programming itself. It’s intended to be
accessible to people who have never coded before at all, in any language. This means
that the first 90-odd pages of the book will be unnecessary for people who are
already familiar with the general principles of programming and especially Pascal
programming. People who know programming but are new to Pascal can skip past
the first 60 pages or so.
I made a deliberate decision to limit this book to about 350 pages. That’s a lot
of paper to print, punch, and bind if you want to make a paper copy for yourself. I
intend to post a spiral-bound paper copy on Amazon KDP soon, though that will
obviously not be a “free” book.
In other words, I want to keep to the mission of a book not only for newcomers
to Pascal, but even newcomers to programming itself.
Here’s a short list of things I will not cover:
• Object-oriented programming (OOP)
• Traditional pointers and linked lists (they’ve been subsumed by OOP Lists)
• GUI building with Lazarus
• Database programming
All of that depends on objects, and Lazarus components are objects. So the next
book, whatever its title, will begin with OOP and move from there to GUI building
with Lazarus and then everything else. I hope to keep my future books shorter, so I
may move database work off to an entirely separate book. I have no timetable at this
point. They’ll happen when they happen. Hang in there. Thanks for downloading,
and double thanks for being interested in Pascal!
—73—
Jeff Duntemann K7JPD
Scottsdale, Arizona, USA