Error Messages: How the FreeBASIC Compiler Tries to Help

...He lunged again, missed, hit the string, which wrapped itself insultingly around the sword-point. Maybe it was simply too difficult for him.

That thought didn't sit comfortably, so he came at the problem from a different angle. Obviously, he told himself, the reason I can't do it is because it's not difficult enough.

K.J. Parker - Devices and Desires

A compiling error is a flaw in a program's source code that prevents the compiler from being able to generate an executable program. The FreeBASIC compiler is just a computer program, and it can not guess about what we intend our code to mean. When it becomes confused, or is missing important information, the best it can do is let you know. It does so by reporting error messages.

For a programmer learning a new language, error messages can feel like they are the only thing standing between you and seeing your ideas come to life. A beginner will generate a lot of source code errors simply because he or she has misunderstood the rules of the language, but even the most experienced programmer sees his fair share of error messages. We all make typos and occasionally forget to take care of one or two details before we go to compile.

Source code errors, however, are the easiest type of mistakes to correct. After all, when a program won't compile, you know it is broken. What's more, the FreeBASIC compiler does everything it can to tell you how it is broken.

The trouble for the beginning programmer, however, is that the compiler does not have the picture that you as the programmer must have. You know how you intend each piece to fit together. The compiler has a totally different view point. Its nose is right up against each line, and it only sees one piece at a time. If a piece is missing, it has no idea what that piece should look like, so when it lets you know that your code is broken, it can be confusing at first to decipher the message it gives you.

With all that in mind, I don't want you to be disappointed when I tell you that this tutorial is not intended to teach you anything about programming in FreeBASIC. It is not even going to teach you how to fix mistakes in your programs.

Instead, the sole purpose of this tutorial is to help you become familiar with seeing compiler error messages and warnings. Instead of waiting for errors to pop up for the first time when you are trying to learn something else, we are going to force a handful of common error messages now, so that you will recognize them in the future.

To do that, we are going to take a functioning program... and break it.

Jargon

If you are a complete novice, you're going to see a lot of words and terms that may be unfamiliar or used in a context you haven't seen before. Don't get too distracted by it. These are the main terms you need to understand.

Source
is a human readable description of a program written in a programming language.
Compiler
is a program that translates source code into a program file.
Executable
is a program created by the compiler.
Message
is information displayed on the screen.
Error
is a mistake.

What you will need

To get the most out of this tutorial, you should actually run the FreeBASIC compiler with the example source code to see for yourself what the output of the error messages looks like on your system. As a programmer, you are going to see these messages a lot, but the more you see them, the easier it will be to understand them. You may as well get started now.

All you need to follow this tutorial

  • The FreeBASIC project
  • A plain text editor

If you haven't already installed FreeBASIC on your system, and you are not sure how to do so, this tutorial might help: Installing FreeBASIC on Windows.

For the purposes on this tutorial, Windows Notepad will do. Just make certain that you save files in plain text format. It is also a good idea to turn of word wrap and enable the status bar.

If you have installed FBIDE, and would prefer to use it to edit and compile the examples, that will work as well. However, the instructions here assume only that you have access to a command console with the FreeBASIC compiler already in the default search path. If you have never run the compiler directly from a command console, you might benefit from this tutorial: A Short Introduction to FreeBASIC Command Line Compiling on Windows

Setting up your work space

Start a command console and your text editor.

If you use the Open Console program that was installed with FreeBASIC, then your current directory should already be set to your FreeBASIC directory, and this location should already be in your PATH environment variable.

If you have a wide enough screen, you may want to arrange your windows in a manner similar to this:

This will allow you to both read this page and follow along without having to move these three windows around too much. If this doesn't work for you, you can experiment with different layouts as you progress.

Create a working directory

To keep your FreeBASIC installation some what clean, we will create a subdirectory under your FreeBASIC directory to store the files we will create. You can delete these files when you are done with them.

In the command console, use these two commands to create a subdirectory named cfTut and to enter that directory after it has been created.

md cfTut

cd cfTut

Your command prompt should look something like this:

C:\Program Files\FreeBASIC\cfTut>

The Victim

Here is the source code for the program that will be the test subject of our experiment.

It doesn't do anything special, and what it does do, it doesn't do that well. Right now, that doesn't concern us. For our purposes, it's just a lab rat.

You can Cut and Paste this program into your text editor, or you can download a copy here:Test.bas

Normally, I would recommend typing it manually, but right now it will be better to have an initial version that we already know has no source code errors.

Either way, save the source code as test.bas in the directory we just created.

'Preprocessor definitions
#Define MAX_NUM 10
#Define MIN_NUM 0
#Macro  MAKE_RANGE( _lb_,_ub_ )
   ( "[" & ( _lb_ ) & " to " & ( _ub_ ) & "]" )
#Endmacro
#Define RANGE_TXT MAKE_RANGE( MIN_NUM, MAX_NUM )

'User data types
Enum Range_Condition
   TOO_LOW
   IN_RANGE
   TOO_HIGH
End enum

'Procedure declarations
Declare Function InRange(byval value as Integer,_
                         byval min   as Integer,_
                         byval max   as Integer )_
                         as Range_Condition

'Program STARTS Here

'Display Description
Print "This program will calculate the factorial"
Print "of the number you supply."
Print ' Blank line
Print "Input may be in the range: " & RANGE_TXT
Print ' Blank Line

'Get user input
Dim as Integer userInput
Dim as Range_Condition check
Do

   Input "Enter a number:", userInput

   'Validate Input
   check = InRange(userInput, MIN_NUM, MAX_NUM )
   Select Case check
   Case TOO_LOW
      Print "Input is too low."
   Case TOO_HIGH
      Print "Input is too high."
   End select
   
Loop until check = IN_RANGE

'Perform Calculation
Dim as Double factorial = 1
For term as Integer = 1 to userInput
   factorial *= term
Next term

'Display Results
Print ' Blank line
Print userInput & "! = " & factorial
Print ' Blank line

'Program ENDS Here


Function InRange(byval value as Integer,_
                 byval min   as Integer,_
                 byval max   as Integer )_
                 as Range_Condition

   If value < min Then
      function = TOO_LOW
   Elseif value > max Then
      function = TOO_HIGH
   Else
      function = IN_RANGE
   End if

End function

Now we can start making mistakes

Compile and Run test.bas

Before we start to demolish this program, let's make sure it is already in working condition. I've already compiled this source about a dozen times... but you shouldn't take my word for it.

In the command console, compile the program with this command:

fbc -exx test.bas

If everything is as it should be, we should receive no output to the console from the compiler.

The -exx compiler option in the command we just used enables run time error checking. Even if a program compiles correctly, this doesn't mean that the program is error free. The compiler creates an executable that does exactly what we described in the source code. Sometimes, we accidentally describe actions that would cause our program to crash. There's little harm in this, but its obviously not what we intended to do. Run time error checking helps us identify and weed out this kind of mistake. It won't catch every problem, but its better than nothing. The compiler must add some extra code to our program to do this, but once we are confident that our program is in full health, we can recompile without the extra code.

When the Source is Good

Now, just to get an idea of what we are working with, go ahead and run the program with this command:

test

I know. It's not the latest killer app, is it? It does have its virtues though. It was the only program I could find that would volunteer for the torture we are going to inflict upon it.

Here's the plan

We are going to make many very small changes to the source code of this program to see how the compiler responds when we give it bad code. We'll make each change one at a time, and restore the program to its original state before the next change. This way we can be sure that the error messages that we get are the result of the specific changes that we make.

Make a backup

The changes that we will make are going to be so minor that it will be really easy to undo them, but just in case things go off track, let's make a back up copy with this command:

copy test.bas test.saved

Now, if for some reason we find that we can't undo our damage we can simply restore the original code with:

copy test.saved test.bas

We'll also have to remember to reload test.bas in our text editor to make sure we are working with the restored copy.

Start at the top

Our first change will be to delete the very first character on the very first line of the source code. That line looks like this:

'Preprocessor definitions

In your text editor, delete that ' symbol in front of the word Preprocessor. Then save the file, and compile the program again. (Remember fbc -exx test.bas )

Here's what we get:

test.bas(1) error 41: Variable not declared, Preprocessor in 'Preprocessor definitions'

Examining the error message

This is a typical compiler message. It has four parts

test.bas(1)
The the compiler found a problem in the file test.bas on line #1.
error 41
In this case we have an error message. The error number helps categorize the message that follows. (This number has uses that don't concern us right now).
Variable not declared
This is a description of what has gone wrong.
Preprocessor in 'Preprocessor definitions'
In this message, this is the symbol or word that caused the trouble and the code where it was found.
  • So, the trouble is on line #1 of test.bas. We knew that.
  • It is an error. That's what we're after.
  • The problem is with the symbol "Preprocessor" Hmm? We didn't change that.
  • The compiler thinks "Preprocessor" is supposed to be a variable, but it hasn't been defined.

Is the message correct?

The little ' symbol that we deleted is the FreeBASIC comment marker. (One of them. There are a couple others). Basically, the purpose of the comment marker ' is to instruct the compiler to ignore everything that follows it on that line. This makes it possible to include information directly in the source code that is not intended to be used as code. In this case, "Preprocessor definitions" was just a little remark about the next few lines of code.

So the problem is that our comment is incorrect, but the compiler complained about an undefined variable.

That's wrong, isn't it? No!

The compiler sets the rules

The FreeBASIC compiler implements the FreeBASIC language. It defines the language. If it says "Preprocessor" is a variable, it is because we told it that is was. If we want "Preprocessor" to be a part of a comment, we have to say so.

It is as if a friend asked for directions, and we said "Turn left," when we meant to say "Go straight". Left means left, and now our friend is lost.

Let's fix it

We, fortunately, do know that we meant that first line to be a comment, so we'll just put the ' symbol back. (It's on the same key as the double quote, right beside the enter key. ) Insert the comment marker before "Preprocessor". Save the file, and if we try to compile again, all should go smoothly.

FreeBASIC doesn't bug out at the first sign of trouble

Line #2 looks like this:

#Define MAX_NUM 10

Delete the # symbol, save, and compile. Now we get:

test.bas(2) error 41: Variable not declared, Define in 'Define MAX_NUM 10'

test.bas(28) error 41: Variable not declared, MAX_NUM in 'Print "Input may be in the range: " & RANGE_TXT'

That's two errors, both of the same type we saw a moment ago. Notice that the first problem is on the line we changed, but the second problem is down on line #28.

But... we didn't touch line #28.

If we look closer at the messages, we see that the symbol MAX_NUM is causing trouble in line #28, and MAX_NUM is in the line that we just changed.

As it turns out, these lines that begin with the # symbol are preprocessor directives. #Define was intended to set up a text replacement macro. Removing that # botched it up, so now the compiler doesn't know what to do with the symbol MAX_NUM when it shows up later.

Now, when the compiler found the error on line #2, we know the program won't compile. Fbc.exe could just quit there, but it doesn't. It forges ahead to look for more errors so that it can give us as much information as possible. It'll keep on reporting trouble as it sees it. Of course, if it finds enough errors, it assumes that we have enough on our hands to deal with and calls it a day.

Go ahead and put the # symbol back in place.

The wrong way to do it right

Now, look at line #3:

#Define MIN_NUM 0

Change it so that the symbol MIN_NUM becomes MN_NUM.

#Define MN_NUM 0

This line is syntactically correct. The compiler won't choke on it like it did with the first two changes we made. Try to compile.

test.bas(28) error 41: Variable not declared, MIN_NUM in 'Print "Input may be in the range: " & RANGE_TXT'

We screwed up line #28 again, but we didn't get an error for the line we changed. It turns out that the change we made resulted in code that was well formed. It created a preprocessor symbol named MN_NUM, and happily moved on. But again, we goofed line #28 and... wait a minute!

These last two changes dorked the symbols MAX_NUM and MIN_NUM, and both caused a problem in line # 28.

But line #28 looks like this:

Print "Input may be in the range: " & RANGE_TXT

MAX_NUM and MIN_NUM aren't even in that line!

I Confess

In truth, I introduced all these preprocessor directives at the top of this program so that I could really misdirect the compiler with small changes.

Not only did I tell my friend to make a left, I sent him into downtown Pittsburgh where everyone will tell him, "You can't get there from here."

What these #Defines and #Macros are doing is instructing a part of the FreeBASIC compiler called the Preprocessor to actually rewrite the source code just before the compiler determines what it is supposed to mean.

This is a pretty handy tool to have, but as you can see, when it goes wrong, it can be somewhat difficult to track down the real problem.

In this case, the problem comes from the symbol RANGE_TXT which depends on all five lines that precede it. A mistake in any of these lines changes the intended meaning of RANGE_TXT, which is in line #28.

Some programmers love the preprocessor. Others never touch it. Most fall in between. It can be used to accomplish huge effects with relatively little effort, but it can also introduce some of the most tangled source code errors.

Seeing through the compiler's eyes

Without fixing the error we just introduced into test.bas, use this command:

fbc -pp test.bas

We're going to the get the same error message, but in addition, we also get a look at what the compiler sees after preprocessing is done. With the -pp option in the command line, the compiler doesn't try to compile the source code. Instead, it preprocesses and checks syntax only, and it saves the results of all those #Define and #Macro symbols in a file ending in .pp.bas. In this case, the file will be test.pp.bas

Open test.pp.bas in a text editor, and take a look.

The first thing you'll notice is that all the comments are gone. All the lines starting with # are gone too. All the lines that were indented in test.bas merely start with a blank space in test.pp.bas.

This is pretty close to how the compiler sees the source code. Almost everything that it doesn't need is gone. (It doesn't need those blank spaces and empty lines, but it helps us a little. )

If you scan down, you'll find what has become of line #28.

Print "Input may be in the range: " & ( "[" & ( MIN_NUM ) & " to " & ( 10 ) & "]" )

This is exactly the code that I intended. The only problem is that the compiler doesn't know what to do with MIN_NUM, thanks to our meddling.

Don't worry if the code makes no sense to you right now. The only goal is to understand how mistakes in one place can show up as source code errors in other places.

If you are getting the general idea of what happened when we changed line #3, then it should be smooth sailing, because that's as complicated as it gets. The knot could have more loops and tie together more pieces, but it would just be more of the same problem.

Let's put test.bas back in working condition by changing MN_NUM back to MIN_NUM on line #3 so that we can move on.

Don't be intimidated

So far we've introduced source code errors that could easily crop up due to simple typing mistakes. It's not hard to forget a comment marker or leave a letter out of an identifier. Most programmers don't write source code from top to bottom. Often the relationship between subsequent parts is not appreciated until the broad outline is sketched. It's usually a back and forth, in and out activity, which creates a lot of chances to make simple errors.

Some mistakes, however, are accidentally intentional.

On line #10, which is,

Enum Range_Condition

change Enum to Enumerate.

Lines #10 to #14 set up a custom data type called an Enumerated Data Type. Don't worry about what that means. The idea here is to pretend that we really did think that Enum was supposed to be Enumerate. We haven't read the manual in some time, and we haven't used an Enumerated Data Type for a while, so we just forgot the right keyword. That's okay. We're learning. (You do have the manual, right? )

So we compile and we get:

test.bas(10) error 41: Variable not declared, Enumerate in 'Enumerate Range_Condition'

test.bas(11) error 41: Variable not declared, TOO_LOW in 'TOO_LOW'

test.bas(12) error 41: Variable not declared, IN_RANGE in 'IN_RANGE'

test.bas(13) error 41: Variable not declared, TOO_HIGH in 'TOO_HIGH'

test.bas(14) error 34: Illegal 'END', found 'End' in 'End enum'

test.bas(20) error 14: Expected identifier, found 'Range_Condition' in 'as Range_Condition'

test.bas(33) error 14: Expected identifier, found 'Range_Condition' in 'Dim as Range_Condition check'

test.bas(39) error 41: Variable not declared, check in 'check = InRange(userInput, MIN_NUM, MAX_NUM )'

test.bas(66) error 14: Expected identifier, found 'Range_Condition' in 'as Range_Condition'

Wow! test.bas is done for. We might as well walk away. If we had so much trouble tracking down that problem with line #28, then we really have our work cut out for us now.

On the other hand, we did invest a bit of time writing this program, and we really want to see it run, so we roll up our sleeves and begin at the beginning.

The first error is on line #10. It looks right to us, but the compiler is saying that Enumerate looks like a variable name, and not the keyword that we think it should be...and remember, the compiler is always right.

We downloaded the help file when we downloaded FreeBASIC, so we pull it up and look in the index for Enumerate.

No dice.

We do see Enum, though, and realize immediately what is causing the error on line #10.

Let's correct it now, and change Enumerate back to Enum.

One down. Eight to go.

Now, while the help file was open, we took the time to reread the description of Enum just to make sure we really understand what it does. When we move on to look at lines #11 through #14, we're at a loss. Everything looks like it does in the manual.

Before we begin to suspect that there's a bug in the compiler, it might be wise to save the correction we made to line #10, and then recompile to see if at least that error goes away.

Just like that, all nine error messages vanish. Looks like all those other source code lines were depending on line #10 to do its job. When it called out sick, they all went home too.

Here's the moral: Don't be fooled by a long list of error messages.

Start with the first problem the compiler found, figure it out, fix it, and compile again. Any error that was directly related to it will be solved at the same time. If you get new errors, then these were problems that the previous mistakes were preventing the compiler from spotting. Keep solving them one at a time. It's not unusual for two or three typos to result in a dozen or so error messages. No matter how many errors pop up, don't get discouraged.

As you develop, the source code mistakes that you make will become more and more trivial, and you'll find that it takes less and less time to correct them. You'll learn to program defensively, and you'll start tackling the real challenge: logical errors.

Errors are Annoying. Warnings Are Dangerous!

Not all messages that the FreeBASIC compiler reports are errors. An error condition prevents the compiler from producing an executable program. There are other conditions where the compiler is designed to alert you that although what it sees poses no problem for the compiler, you might not have intended the results. These are warnings.

Warnings are worse than errors. When you have a source code error, you either fix it, or you don't get a program. When the compiler gives you a warning, you still get an executable program, and often it does exactly what you meant it to do. So you get into the habit of simply ignoring warnings. That's when trouble strikes.

The optional requirement

One type of warning will illustrate the point.

If you haven't put test.bas back into service ready status, do so now. You still have the backup if you need it.

Make sure the code will compile, and run the program again to refresh your memory concerning what it looked like.

This program will calculate the factorial
of the number you supply.

Input may be in the range: [0 to 10]

Enter a number:5

5! = 120

When you are done, look at line #25

Print "This program will calculate the factorial"

The purpose of this statement is to output the string of textual data between the two double quotation marks. The first quotation mark is the opening quote and the second is the closing quote.

Every opening quote must have a closing quote... almost.

Remove the double quotation mark from the end of the line to make it look like this:

Print "This program will calculate the factorial

Save and compile.

Here's our warning:

test.bas(25) warning 13(0): Missing closing quote in literal string

The program will still successfully compile, and the program will behave exactly the same way as it did before.

This worked because in this case, the compiler is not expecting anything to come after the string, so it just treats everything after the opening quote until the end of the line as part of the string. Then it warns you that something looked fishy.

Some might say, "Hey, I don't need that closing quote."

Now, remove the closing quote from line #28. This time it is not at the end of the line. It is before the & symbol. Just delete that quote, save again, and recompile.

test.bas(25) warning 13(0): Missing closing quote in literal string

test.bas(28) warning 13(0): Missing closing quote in literal string

Now we get two warnings about missing closing quotes. Well, the warning for line #25 wasn't an issue, and we're sick of dealing with line #28. Things are probably just fine.

Now run the program and see if you notice what happened.

This program will calculate the factorial
of the number you supply.

Input may be in the range:  & RANGE_TXT

Enter a number:5

5! = 120

That's an error in the program. That & RANGE_TXT was not intended for output. We were warned.

"Big deal!" we can here some cry. "You spotted it the first time you ran the program. It's easy to fix."

We respond, "What if that string had been destined for something other than the console window? What if it had been stored in a file, not to be accessed for quite a while? What if the information that was supposed be output was important to someone?"

The point is that the compiler warns you when you do something risky. For every warning condition, there is always a way to code your intentions in such manner that tells the compiler, "Yes. That is what I wanted." By forcing yourself to rewrite code that causes warnings, you are that much more certain that your program should work properly.

Not all messages come from the compiler

The FreeBASIC compiler actually relies on a few external programs to get its job done. It handles the preprocessing and the interpretation of your source code, then it hands the rest of the process off to other programs to do the grunt work. On occasion, a problem in our source code doesn't stop the compiler, but stalls one of the helper programs.

Find line #63.

Function InRange(byval value as Integer,_

Change InRange to IsInRange and recompile.

We get this:

test.o:fake:(.text+0xeb): undefined reference to `INRANGE@12'

This doesn't look like any of the messages we've seen before. We have to look pretty close before INRANGE@12 begins to resemble anything in our program.

This is a linker error. The linker is the program that takes bits and pieces of compiled code and finally stitches them together into an executable program.

What has gone wrong here, it that on line #39 we use the symbol InRange, but this symbol hasn't been defined before line #39. Normally, the compiler would give us another undeclared variable message, but in this case we did declare the variable. Way back at line #17 we told the compiler that somewhere a function answering to the name of InRange does in fact exist. It could be in this source code file, some other source code file that we are going to compile at the same time, or some object code file that has already been compiled.

Before we made our change, InRange was finally defined down at the bottom of our source code, where the compiler eventually found it. Now, though, InRange is not defined, so the compiler assumes that it must exist elsewhere. It is the linker's job to find it. It can't because it doesn't exist. No other code is coming to rescue test.bas.

We can fix it by changing IsInRange back to InRange , or we can change line #39 and line #17 to match the new name.

What now?

I am sure that by now you would rather not see another error message for a long, long time. I have gone to the trouble of writing this tutorial so that you can spend more time learning the FreeBASIC language, and less time wondering why something doesn't work when the real issue is as simple as a missing coma, or a misspelled keyword.

Additionally, if you followed along and actually made the changes to test.bas and ran the compiler, you've mastered the edit-compile-correct-compile cycle of a single file program.

If you are not yet tired of kicking test.bas around, here is another programming error you can introduce that won't give you an error message: On line #52, delete the * symbol from before the = symbol. If you save, compile, and run the program; see if you spot the bug.

By the way, there's been a bug in this program the entire time, and it has nothing to do with incorrect source code, and no change to just one or two lines will fix it. After you fix the bug I asked you to place on line #52, run the program and give it as many different values for input as you can think up. You'll find that for a certain set of numbers, part of the output is contradictory. I won't tell you what it is, but I will say that it has nothing to do with entering letters instead of numbers.