Computer Science II Lab 3
Parsing the Command Line and Building Complex Programs
Introduction
============
This lab will guide you through building programs which have
multiple files, as well as automating the build process. This is a
review of some of the CS I material, but it also pushes your uses of
classes just a little farther.
We will also be adding a couple of functions to ansi.h, which will
allow us to hide and show the cursor. I had originally left these
out, and now I want to correct that!
At the end of this lab, you should have a clear picture of class
scoping, how command line arguments are processed, and how to write
simple makefiles. The command line parser presented in this lab
will be used in future labs, so be sure you get it right!
First, we do your addendum for ansi.h.
Guided Lab Part 1 - A slight addition to ansi.h
===============================================
In lab 1, we wrote a nifty little file which allows us to color
things on the terminal and make cool animation like effects. I had
omitted one sequence because, at the time, I thought it wasn't as
universal as it appears to actually be. These functions are the ones
which will allow us to turn the cursor on and off. That would have
made the game of life so much better! Well, live and learn. We
won't be using these in this actual lab, but they will be used in
our in-class examples, so you may as well add them.
So, simply add the following lines to ansi.h:
--begin end of ansi.h--
inline std::ostream& cursorOff(std::ostream &os) {
return os << "\033[?25l";
}
inline std::ostream& cursorOn(std::ostream &os) {
return os << "\033[?25h";
}
--end end of ansi.h--
Now you can turn the cursor off with:
cout << cursorOff;
And you can turn the cursor on with:
cout << cursorOn;
Now that we're done with that, let's dive into the main part of the
lab!
Parsing the Command Line (Guided)
=================================
As we know from class, the UNIX command line is passed into a
program via the argc and argv arguments to the main function. argc
contains the number of arguments passed into the progamm and argv is
a ragged array of strings containing the values of the command line
arguments. argv[0] is the name of the program as invoked at the
command line and the rest are the space delimited words which follow
the name of the program.
Now, there is a general format users have come to expected.
Basically there are two categories of arguments. There are unnamed
arguments, which are just values, and then there are named arguments
or "switches" that they can turn on. Literal values are specified
by themselves, and switches begin with a -.
In addition to these rules, there is a second rule. Namely, a
single - denotes a single character switch, and -- denotes an entire
word being used. Take for instance, the mysql command line client.
I could log into a database as follows:
mysql -ubob -pfoo database_name
Or
mysql --user bob -pfoo database_name
Note how -u and --user are equivalent. This is definitely the norm!
The long words are easier to remember, while the short ones are
shorter for typing. So most switches come in both forms. Also
because -u is a single character "-u bob" and "-ubob" are
equivalent. Thus they both set the internal variable user to
"bob".
So now, how can we parse all that? Well, we could establish some
rules. The rules go like this:
1.) If a string starts with "--" then this is a full word
argument. Check the next string, if it does not start with
"-" then it is the value for the switch. Thus the switch gets
a flag and a value, or it's a flag with a blank value.
2.) If a string starts with "-" then this is a single character
argument. If the string contains more than one character
after the "-", that is the value. Otherwise the next string
is the value if it does not begin with "-". The result is as
above in number 1.
3.) Otherwise, the string is a value without a switch/flag.
Return a blank flag and the value.
Now these rules aren't terribly complex, but they can be subtle in
their implementation. So it would be best to write this once and
then reuse the parser. We will do this using a class. The class
will keep track of our position in the argument list and let us
cycle through the strings and it will convert them into the two part
arguments as it goes. We will implement this in the files
cmdParser.h and cmdParser.cpp. These files follow. Go ahead and
type them in now. Pay close attention to how the class does its
job. Look at when the variables are available, and ask questions if
you don't understand how it does its job!
--begin cmdParser.h--
1 /*
2 * File: cmdParser.h
3 * Purpose: The header for a class used for parsing command line arguments.
4 * There are three kinds of arguments. The first are single letter
5 * flags and they begin with -, and then there are multiple letter
6 * flags which begin with -- and then there are non-flagged values
7 * which do not begin with -.
8 * thus -a1 and -a 1 are both "a=1"
9 * --add 1 is add=1
10 * foo is a null flag, with value "foo"
11 * Author: Robert Lowe
12 */
13 #ifndef PARSER_H
14 #define PARSER_H
15 #include <string>
16
17 //the command line argument returned by the parser
18 typedef struct argument {
19 std::string flag; //the argument itself (named, if there is one)
20 std::string value; //the value of the argument (if there is one)
21 } Argument;
22
23
24 class CmdParser {
25 public:
26 // Constructor
27 CmdParser(int argc, char **argv);
28
29 //pull out the next command line argument
30 Argument nextArgument();
31
32 //returns true if there is a next argument, false otherwise
33 bool hasNext();
34
35 private:
36 int argc; //argument count
37 char **argv; //argument values as passed into constructor
38 int index; //current index of the
39 };
40
41 #endif
--end cmdParser.h--
--begin cmdParser.cpp--
1 #include "cmdParser.h"
2 #include <string>
3
4 using namespace std;
5
6 // Constructor
7 CmdParser::CmdParser(int argc, char **argv) {
8 //set up the argc and argv
9 this->argc = argc;
10 this->argv = argv;
11
12 //setup the index. We skip the program name
13 index = 1;
14 }
15
16
17 //pull out the next commmand line argument
18 Argument
19 CmdParser::nextArgument() {
20 Argument result; //the result
21
22 //fail safely if someone tries to read too much
23 if(index >= argc)
24 return result;
25
26 //if block to handle arguments
27 if(argv[index][0] != '-') {
28 //handle null flags
29 result.value = argv[index];
30 } else {
31 //this is a flag!
32 if(argv[index][1] == '-') {
33 //handle the multiple character flag
34 result.flag = argv[index]+2;
35 } else {
36 //handle single character flag
37 result.flag = argv[index][1];
38
39 //there is a chance the value part is in the same string
40 if(argv[index][2]!='\0') {
41 result.value = argv[index] + 2;
42 }
43 }
44 }
45
46 //advance the index
47 index++;
48
49 //ok, so now if we don't yet have a value, look at the next argument.
50 //if it isn't a flag, it's a value, so pull it in!
51 if(result.value.empty() && index < argc && argv[index][0]!='-') {
52 result.value = argv[index];
53 index++; //go to the next index
54 }
55
56 return result;
57 }
58
59
60 //returns true if there is a next argument, false otherwise
61 bool
62 CmdParser::hasNext() {
63 //basically, we are just comparing index to the argc
64 return index < argc;
65 }
--end cmdParser.cpp--
Now that we have that, let's get a test running. Here's one that
just dumps out the parsed values to the screen:
--begin testCmdParser.cpp--
1 #include <iostream>
2 #include <string>
3 #include "cmdParser.h"
4
5
6 using namespace std;
7
8 int main(int argc, char **argv) {
9 CmdParser args(argc, argv); //parser for command line arguments
10 Argument argument;
11
12 //process all arguments
13 while(args.hasNext()) {
14 //get the argument and show it
15 argument = args.nextArgument();
16
17 //print the flag if there is one
18 if(!argument.flag.empty()) {
19 cout << argument.flag << ": ";
20 }
21
22 cout << argument.value << endl;
23 }
24
25 return 0;
26 }
--end testCmdParser.cpp--
Now, let's test it all! Compile with:
g++ testCmdParser.cpp cmdParser.cpp -o testCmdParser
Run your generated program, try giving it some command line
arguments and see how it works. Study the test file and make sure
you understand how to use the class.
Now, let's use this to write a usable program. We will write the
most super deluxe hello world program known to mankind! This
program lets you change the words, the color, displays a help
message, and it parses command lines! Here's the program:
To compile this one, we'll create a makefile. Recall that makefiles
are used to automate the build process. Also, the indentations on
the line are accomplished with literal tabs. This makefile will
build both the test program and the hello program. Here it is:
Now to verify that it works, run the following commands:
gmake clean
gmake
Correct any compile errors, and try again. From here on in, gmake
is your build command! (or make if you are on a system where you
have access to traditional UNIX make).
Study the hello.cpp file. Make sure you understand how it works.
Play around with running the program. Also study the makefile. The
challenge is coming!
Study the structure of this program carefully. All labs after this
one will require multiple header and cpp files, make sure you
understand what each are doing!
A Commmand Line Calculator (Challenge)
======================================
Ok, now it's time for you to fly on your own! You will use our
little command line library to build a simple calculator. Our
calculator will have the following functions:
It will take in arguments from left to right and compute the
result. You do not need to worry about order of operations. The
basic idea is that I can type in something like this:
./calc 40 -a 2 --sub 6 -m 5
And I will get 180. ((40+2) - 6) * 5).
Also, you should add a -h option which prints a helpful message
about how to use your program!
HINT: You will need to convert strings to numbers from the
arguments. The wise student would google for "atof" and especially
"using atof with c++ strings".
Extra Credit Opportunity
========================
Add the following:
--sin Sine (radians)
--cos Cosine (radians)
--tan Tangent (radians)
--sqrt Square root
-e, --pow Exponent 2 -e 4 is 16.
These may seem easy at first, except note that you have to process
things a little differently. For example. Consider the following:
./calc 50 -a --sin 1.2
Note that I have to evaluate --sin 1.2 before I can add! If you try
this, I wish you luck! (It is doable using only what we have
covered so far.)