7. Completing a Program
7.1 Introduction
Now that we are "done" with our program, let's actually complete it!
7.2 Input and output

Let's add a simple prompt for input: >
7.3 Error handling

Now we should try to break our program. Let's generate many instances of "bad" input, like:
-
4 + + ;
-
6 + ( 4 * ) - 2 ;
-
3 ;;;;;
-
(1+);
Try "ridiculous" input that no one should try:
foozziefabblebop
.
Our book author used to feed his emails into the
compiler.
7.4 Negative numbers

Our program does not handle negative numbers properly. It is typical that we only find out if our design is correct by coding it: the computer is the only real test as to whether the design works. To implement unary minus (and plus -- might as well!) we change our grammar for primary to:
Primary:
Number
"(" Expression ")"
"-" Primary
"+" Primary
Changing the code accordingly is so simple it might just work the first run!
7.5 Remainder: %

Adding modulo (%) should be easy: we add it as a
Token
, and define a meaning for it.
The meaning for integer operands is clear and
unambiguous. But what about for floating point numbers?
There is no perfect answer. But often, modulo
for floats is defined as:
x%y==x-y*int(x/y)
The standard library function fmod()
from <cmath>
implements that
definition. So let's modify term()
to include:
case '%':
{
double d = primary(ts);
if (d == 0) error("divide by zero");
left = fmod(left, d);
t = ts.get();
break;
}
As our textbook notes, we could have also decided to forbid non-integer args to modulo.
7.6 Cleaning up the code

We should never consider our code done until it is in a state where we could hand it off to someone else for maintenance, without having to rely on us being by their side to understand what is going on.
7.6.1 Symbolic constants

We can make a big step forward in both the readability and maintainability of our program by defining some symbolic constants. In particular, we will want them for '8', ';', and 'q', for input, and for '>' and '=' for output;
7.6.2 Use of functions
Our functions should name and isolate logically
separate parts of our program. Most of our
functions so far are pretty good in this respect:
expression()
, term()
,
and primary()
directly express our
grammar, get()
handles fetchijng
tokens from our input. But at this point
main()
both provides the "scaffolding"
for the whole program, and also handles the
calculation loop. Let's divide that into two
functions to reflect this logical division.
void calculate(Token_stream& ts)
{
while(cin) {
cout << prompt;
Token t = ts.get();
// this output is for debugging:
// cout << "in main(), got token: " << t.kind
// << " with val of " << t.value << '\n';
while(t.kind == print) t = ts.get();
if(t.kind == quit) return;
ts.putback(t);
cout << result << expression(ts) << '\n';
}
}
Drill
Write the baove function in your calculator source
code file, and then
modify your main()
to that it uses
that function.
(This is called re-factoring your code.)
7.6.3 Code layout
The way we format our code can make a big
difference as to its readability. For instance,
while C++ compilers don't care aout indentation,
"proper" indentation can make a huge difference to
us as human readers of code.
Our textbook author suggests we re-organize
Token_stream::get()
so that the first set of
cases are on separate lines.
7.6.4 Commenting
Write comments only to express the intentions of your code that you can't make obvious by the code itself. For instance, never write:
int x = a + b; // add a and b and assign the result to x
7.7 Recovering from errors

"Quality error handling is one mark of a professional." -- Bjarne Stroustrup
Instead of exiting on an error, let's clean up our mess and get ready to accept more input! Here is our first try at cleaning up:
void clean_up_mess() // naive version
{
while(true) { // skip until we hit a print
Token t = ts.get();
if(t.kind == print) return;
}
}
Nice try, but not quite there. To see why, try:
1@z; 1+3;
A better approach is to build this into
Token_stream
:
void Token_stream::ignore(char c)
// c is token kind to look for
{
// first check buffer:
if(full && c == buffer.kind) {
full = false;
return;
}
full = false;
// now search input:
char ch = 0;
while(cin >> ch)
if(ch == c) return;
}
Remember: this must also be declared in the class interface!
7.8 Variables
7.8.1 Variables and definitions
7.8.2 Introducing names
7.8.3 Predefined names
7.8.4 Are we there yet?
Test Yourself!

- Why did we introduce a const name like 'number' into our program?
- to make the program more maintainable
- to make it easier to change the char for a number
- all answers are correct
- to make the program more readable
- When should you start to test your program?
- only when done coding
- testing isn't really necessary
- almost as soon as you've started coding
- When should you retest?
- Only after major changes.
- After every change, no matter how small.
- One round of testing is enough.
- What is wrong with commenting x = a + b; with // add a and b and put value in x
- It does not describe the code correctly.
- Variable names should never occur in comments.
- It will confuse the compiler.
- It only says what the code already clearly says.
- What is the purpose of commenting in a program?
- It is necessary for the compilation of the code
- None of the above
- It is something not to be bothered about
- To make the program more readable and easy to understand
- What is the use of symbolic constant?
- Nothing much useful in it
- The code is less error prone, easier to change and understand
- None of the above
- Symbolic constants help the code to run faster
Answers
1. c; 2. c; 3. b; 4. d; 5. d; 6. b;
Drill
