27.8 Program Files
The following examples contain the
complete
listing of our program, by file. They are listed here for reference:
Example 27-3. stat/ch_type.h
/********************************************************
* char_type -- Character type class *
* *
* Member functions: *
* type -- returns the type of a character. *
* (Limited to simple types) *
* is(ch, char_type) -- check to see if ch is *
* a member of the given type. *
* (Works for derrived types as well.) *
********************************************************/
class char_type {
public:
enum CHAR_TYPE {
C_EOF, // End of file character
C_WHITE, // Whitespace or control character
C_NEWLINE, // A newline character
C_ALPHA, // A Letter (includes _)
C_DIGIT, // A Number
C_OPERATOR, // Random operator
C_SLASH, // The character '/'
C_L_PAREN, // The character '('
C_R_PAREN, // The character ')'
C_L_CURLY, // The character '{'
C_R_CURLY, // The character '}'
C_SINGLE, // The character '\''
C_DOUBLE, // The character '"'
// End of simple types, more complex, derrived types follow
C_HEX_DIGIT,// Hexidecimal digit
C_ALPHA_NUMERIC // Alpha numeric
};
private:
static enum CHAR_TYPE type_info[256]; // Information on each character
// Fill in a range of type info stuff
void fill_range(int start, int end, CHAR_TYPE type);
public:
char_type( ); // Initialize the data
// ~char_type -- default destructor
// Returns true if character is a given type
int is(int ch, CHAR_TYPE kind);
CHAR_TYPE type(int ch);
};
Example 27-4. stat/ch_type.cpp
/********************************************************
* ch_type package *
* *
* The class ch_type is used to tell the type of *
* various characters. *
* *
* The main member functions are: *
* is -- True if the character is the indicated *
* type. *
* type -- Return type of character. *
********************************************************/
#include <iostream>
#include <assert.h>
#include "ch_type.h"
// Define the type information array
char_type::CHAR_TYPE char_type::type_info[256];
/********************************************************
* fill_range -- fill in a range of types for the *
* character type class *
* *
* Parameters *
* start, end -- range of items to fill in *
* type -- type to use for filling *
********************************************************/
void char_type::fill_range(int start, int end, CHAR_TYPE type)
{
int cur_ch;
for (cur_ch = start; cur_ch <= end; ++cur_ch) {
assert(cur_ch >= 0);
assert(cur_ch < sizeof(type_info)/sizeof(type_info[0]));
type_info[cur_ch] = type;
}
}
/*********************************************************
* char_type::char_type -- initialize the char type table*
*********************************************************/
char_type::char_type( )
{
fill_range(0, 255, C_WHITE);
fill_range('A', 'Z', C_ALPHA);
fill_range('a', 'z', C_ALPHA);
type_info['_'] = C_ALPHA;
fill_range('0', '9', C_DIGIT);
type_info['!'] = C_OPERATOR;
type_info['#'] = C_OPERATOR;
type_info['$'] = C_OPERATOR;
type_info['%'] = C_OPERATOR;
type_info['^'] = C_OPERATOR;
type_info['&'] = C_OPERATOR;
type_info['*'] = C_OPERATOR;
type_info['-'] = C_OPERATOR;
type_info['+'] = C_OPERATOR;
type_info['='] = C_OPERATOR;
type_info['|'] = C_OPERATOR;
type_info['~'] = C_OPERATOR;
type_info[','] = C_OPERATOR;
type_info[':'] = C_OPERATOR;
type_info['?'] = C_OPERATOR;
type_info['.'] = C_OPERATOR;
type_info['<'] = C_OPERATOR;
type_info['>'] = C_OPERATOR;
type_info['/'] = C_SLASH;
type_info['\n'] = C_NEWLINE;
type_info['('] = C_L_PAREN;
type_info[')'] = C_R_PAREN;
type_info['{'] = C_L_CURLY;
type_info['}'] = C_R_CURLY;
type_info['"'] = C_DOUBLE;
type_info['\''] = C_SINGLE;
}
int char_type::is(int ch, CHAR_TYPE kind)
{
if (ch == EOF) return (kind == C_EOF);
switch (kind) {
case C_HEX_DIGIT:
assert(ch >= 0);
assert(ch < sizeof(type_info)/sizeof(type_info[0]));
if (type_info[ch] == C_DIGIT)
return (1);
if ((ch >= 'A') && (ch <= 'F'))
return (1);
if ((ch >= 'a') && (ch <= 'f'))
return (1);
return (0);
case C_ALPHA_NUMERIC:
assert(ch >= 0);
assert(ch < sizeof(type_info)/sizeof(type_info[0]));
return ((type_info[ch] == C_ALPHA) ||
(type_info[ch] == C_DIGIT));
default:
assert(ch >= 0);
assert(ch < sizeof(type_info)/sizeof(type_info[0]));
return (type_info[ch] == kind);
}
};
char_type::CHAR_TYPE char_type::type(const int ch) {
if (ch == EOF) return (C_EOF);
assert(ch >= 0);
assert(ch < sizeof(type_info)/sizeof(type_info[0]));
return (type_info[ch]);
}
Example 27-5. stat/token.h
#include <string>
#include <iostream>
/********************************************************
* token -- token handling module *
* *
* Functions: *
* next_token -- get the next token from the input *
********************************************************/
/*
* A list of tokens
* Note, how this list is used depends on defining the macro T.
* This macro is used for defining the tokens types themselves
* as well as the string version of the tokens.
*/
#define TOKEN_LIST \
T(T_NUMBER), /* Simple number (floating point or integer) */ \
T(T_STRING), /* String or character constant */ \
T(T_COMMENT), /* Comment */ \
T(T_NEWLINE), /* Newline character */ \
T(T_OPERATOR), /* Arithmetic operator */ \
T(T_L_PAREN), /* Character "(" */ \
T(T_R_PAREN), /* Character ")" */ \
T(T_L_CURLY), /* Character "{" */ \
T(T_R_CURLY), /* Character "}" */ \
T(T_ID), /* Identifier */ \
T(T_EOF) /* End of File */
/*
* Define the enumerated list of tokens.
* This makes use of a trick using the T macro
* and our TOKEN_LIST
*/
#define T(x) x // Define T( ) as the name
enum TOKEN_TYPE {
TOKEN_LIST
};
#undef T // Remove old temporary macro
// A list of the names of the tokens
extern const char *const TOKEN_NAMES[];
/********************************************************
* input_file -- data from the input file *
* *
* The current two characters are store in *
* cur_char and next_char *
* *
* The member function read_char moves eveyone up *
* one character. *
* *
* The line is buffered and output everytime a newline *
* is passed. *
********************************************************/
class input_file: public std::ifstream {
private:
std::string line; // Current line
public:
int cur_char; // Current character (can be EOF)
int next_char; // Next character (can be EOF)
/*
* Initialize the input file and read the first 2
* characters.
*/
input_file(const char *const name) :
std::ifstream(name),
line("")
{
if (bad( ))
return;
cur_char = get( );
next_char = get( );
}
/*
* Write the line to the screen
*/
void flush_line( ) {
std::cout << line;
std::cout.flush( );
line = "";
}
/*
* Advance one character
*/
void read_char( ) {
line += cur_char;
cur_char = next_char;
next_char = get( );
}
};
/********************************************************
* token class *
* *
* Reads the next token in the input stream *
* and returns its type. *
********************************************************/
class token {
private:
// True if we are in the middle of a comment
int in_comment;
// True if we need to read a character
// (This hack is designed to get the new lines right)
int need_to_read_one;
// Read a /* */ style comment
TOKEN_TYPE read_comment(input_file& in_file);
public:
token( ) {
in_comment = false;
need_to_read_one = 0;
}
// Return the next token in the stream
TOKEN_TYPE next_token(input_file& in_file);
};
Example 27-6. stat/token.cpp
/********************************************************
* token -- token handling module *
* *
* Functions: *
* next_token -- get the next token from the input *
********************************************************/
#include <fstream>
#include <cstdlib>
#include "ch_type.h"
#include "token.h"
/*
* Define the token name list
* This makes use of a trick using the T macro
* and our TOKEN_LIST
*/
#define T(x) #x // Define x as a string
const char *const TOKEN_NAMES[] = {
TOKEN_LIST
};
#undef T // Remove old temporary macro
static char_type char_type; // Character type information
/********************************************************
* read_comment -- read in a comment *
* *
* Parameters *
* in_file -- file to read *
* *
* Returns *
* Token read. Can be a T_COMMENT or T_NEW_LINE *
* depending on what we read. *
* *
* Multi-line comments are split into multiple *
* tokens. *
********************************************************/
TOKEN_TYPE token::read_comment(input_file& in_file)
{
if (in_file.cur_char == '\n') {
in_file.read_char( );
return (T_NEWLINE);
}
while (true) {
in_comment = true;
if (in_file.cur_char == EOF) {
std::cerr << "Error: EOF inside comment\n";
return (T_EOF);
}
if (in_file.cur_char == '\n')
return (T_COMMENT);
if ((in_file.cur_char == '*') &&
(in_file.next_char == '/')) {
in_comment = false;
// Skip past the ending */
in_file.read_char( );
in_file.read_char( );
return (T_COMMENT);
}
in_file.read_char( );
}
}
/********************************************************
* next_token -- read the next token in an input stream *
* *
* Parameters *
* in_file -- file to read *
* *
* Returns *
* next token *
********************************************************/
TOKEN_TYPE token::next_token(input_file& in_file)
{
if (need_to_read_one)
in_file.read_char( );
need_to_read_one = 0;
if (in_comment)
return (read_comment(in_file));
while (char_type.is(in_file.cur_char, char_type::C_WHITE)) {
in_file.read_char( );
}
if (in_file.cur_char == EOF)
return (T_EOF);
switch (char_type.type(in_file.cur_char)) {
case char_type::C_NEWLINE:
in_file.read_char( );
return (T_NEWLINE);
case char_type::C_ALPHA:
while (char_type.is(in_file.cur_char,
char_type::C_ALPHA_NUMERIC))
in_file.read_char( );
return (T_ID);
case char_type::C_DIGIT:
in_file.read_char( );
if ((in_file.cur_char == 'X') || (in_file.cur_char == 'x')) {
in_file.read_char( );
while (char_type.is(in_file.cur_char,
char_type::C_HEX_DIGIT)) {
in_file.read_char( );
}
return (T_NUMBER);
}
while (char_type.is(in_file.cur_char, char_type::C_DIGIT))
in_file.read_char( );
return (T_NUMBER);
case char_type::C_SLASH:
// Check for /* characters
if (in_file.next_char == '*') {
return (read_comment(in_file));
}
// Now check for double slash comments
if (in_file.next_char == '/') {
while (true) {
// Comment starting with // and ending with EOF is legal
if (in_file.cur_char == EOF)
return (T_COMMENT);
if (in_file.cur_char == '\n')
return (T_COMMENT);
in_file.read_char( );
}
}
// Fall through
case char_type::C_OPERATOR:
in_file.read_char( );
return (T_OPERATOR);
case char_type::C_L_PAREN:
in_file.read_char( );
return (T_L_PAREN);
case char_type::C_R_PAREN:
in_file.read_char( );
return (T_R_PAREN);
case char_type::C_L_CURLY:
in_file.read_char( );
return (T_L_CURLY);
case char_type::C_R_CURLY:
in_file.read_char( );
return (T_R_CURLY);
case char_type::C_DOUBLE:
while (true) {
in_file.read_char( );
// Check for end of string
if (in_file.cur_char == '"')
break;
// Escape character, then skip the next character
if (in_file.cur_char == '\\')
in_file.read_char( );
}
in_file.read_char( );
return (T_STRING);
case char_type::C_SINGLE:
while (true) {
in_file.read_char( );
// Check for end of character
if (in_file.cur_char == '\'')
break;
// Escape character, then skip the next character
if (in_file.cur_char == '\\')
in_file.read_char( );
}
in_file.read_char( );
return (T_STRING);
default:
assert("Internal error: Very strange character" != 0);
}
assert("Internal error: We should never get here" != 0);
return (T_EOF); // Should never get here either
}
Example 27-7. stat/stat.cpp
/********************************************************
* stat *
* Produce statistics about a program *
* *
* Usage: *
* stat [options] <file-list> *
* *
********************************************************/
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <cstring>
#include <assert.h>
#include "ch_type.h"
#include "token.h"
/********************************************************
* stat -- general purpose statistic *
* *
* Member functions: *
* take_token -- receives token and uses it to *
* compute statistic *
* line_start -- output stat at the beginning of *
* a line. *
* eof -- output stat at the end of the file *
********************************************************/
class a_stat {
public:
virtual void take_token(TOKEN_TYPE token) = 0;
virtual void line_start( ) {};
virtual void eof( ) {};
// Default constructor
// Default destructor
// Copy constructor defaults as well (probably not used)
};
/********************************************************
* line_counter -- handle line number / line count *
* stat. *
* *
* Counts the number of T_NEW_LINE tokens seen and *
* output the current line number at the beginning *
* of the line. *
* *
* At EOF it will output the total number of lines *
********************************************************/
class line_counter: public a_stat {
private:
int cur_line; // Line number for the current line
public:
// Initialize the line counter -- to zero
line_counter( ) {
cur_line = 0;
};
// Default destrctor
// Default copy constructor (probably never called)
// Consume tokens, count the number of new line tokens
void take_token(TOKEN_TYPE token) {
if (token == T_NEWLINE)
++cur_line;
}
// Output start of line statistics
// namely the current line number
void line_start( ) {
std::cout << std::setw(4) << cur_line << ' ' << std::setw(0);
}
// Output eof statistics
// namely the total number of lines
void eof( ) {
std::cout << "Total number of lines: " << cur_line << '\n';
}
};
/********************************************************
* paren_count -- count the nesting level of ( ) *
* *
* Counts the number of T_L_PAREN vs T_R_PAREN tokens *
* and writes the current nesting level at the beginning*
* of each line. *
* *
* Also keeps track of the maximum nesting level. *
********************************************************/
class paren_counter: public a_stat {
private:
int cur_level; // Current nesting level
int max_level; // Maximum nesting level
public:
// Initialize the counter
paren_counter( ) {
cur_level = 0;
max_level = 0;
};
// Default destructor
// Default copy constructor (probably never called)
// Consume tokens, count the nesting of ( )
void take_token(TOKEN_TYPE token) {
switch (token) {
case T_L_PAREN:
++cur_level;
if (cur_level > max_level)
max_level = cur_level;
break;
case T_R_PAREN:
--cur_level;
break;
default:
// Ignore
break;
}
}
// Output start of line statistics
// namely the current line number
void line_start( ) {
std::cout.setf(std::ios::left);
std::cout.width(2);
std::cout << '(' << cur_level << ' ';
std::cout.unsetf(std::ios::left);
std::cout.width( );
}
// Output eof statistics
// namely the total number of lines
void eof( ) {
std::cout << "Maximum nesting of ( ) : " << max_level << '\n';
}
};
/********************************************************
* brace_counter -- count the nesting level of {} *
* *
* Counts the number of T_L_CURLY vs T_R_CURLY tokens *
* and writes the current nesting level at the beginning*
* of each line. *
* *
* Also keeps track of the maximum nesting level. *
* *
* Note: brace_counter and paren_counter should *
* probably be combined. *
********************************************************/
class brace_counter: public a_stat {
private:
int cur_level; // Current nesting level
int max_level; // Maximum nesting level
public:
// Initialize the counter
brace_counter( ) {
cur_level = 0;
max_level = 0;
};
// Default destructor
// Default copy constructor (probably never called)
// Consume tokens, count the nesting of ( )
void take_token(TOKEN_TYPE token) {
switch (token) {
case T_L_CURLY:
++cur_level;
if (cur_level > max_level)
max_level = cur_level;
break;
case T_R_CURLY:
--cur_level;
break;
default:
// Ignore
break;
}
}
// Output start of line statistics
// namely the current line number
void line_start( ) {
std::cout.setf(std::ios::left);
std::cout.width(2);
std::cout << '{' << cur_level << ' ';
std::cout.unsetf(std::ios::left);
std::cout.width( );
}
// Output eof statistics
// namely the total number of lines
void eof( ) {
std::cout << "Maximum nesting of {} : " << max_level << '\n';
}
};
/********************************************************
* comment_counter -- count the number of lines *
* with and without comments. *
* *
* Outputs nothing at the beginning of each line, but *
* will output a ratio at the end of file *
* *
* Note: This class makes use of two bits: *
* CF_COMMENT -- a comment was seen *
* CF_CODE -- code was seen *
* to collect statistics. *
* *
* These are combined to form an index into the counter *
* array so the value of these two bits is very *
* important. *
********************************************************/
static const int CF_COMMENT = (1<<0); // Line contains comment
static const int CF_CODE = (1<<1); // Line contains code
// These bits are combined to form the statistics
//
// 0 -- [0] Blank line
// CF_COMMENT -- [1] Comment only line
// CF_CODE -- [2] Code only line
// CF_COMMENT|CF_CODE -- [3] Comments and code on this line
class comment_counter: public a_stat {
private:
int counters[4]; // Count of various types of stats.
int flags; // Flags for the current line
public:
// Initialize the counters
comment_counter( ) {
memset(counters, '\0', sizeof(counters));
flags = 0;
};
// Default destructor
// Default copy constructor (probably never called)
// Consume tokens, count the nesting of ( )
void take_token(TOKEN_TYPE token) {
switch (token) {
case T_COMMENT:
flags |= CF_COMMENT;
break;
default:
flags |= CF_CODE;
break;
case T_NEWLINE:
assert(flags >= 0);
assert(flags < sizeof(counters)/sizeof(counters[0]));
++counters[flags];
flags = 0;
break;
}
}
// void line_start( ) -- defaults to base
// Output eof statistics
// namely the total number of lines
void eof( ) {
std::cout << "Number of blank lines ................." <<
counters[0] << '\n';
std::cout << "Number of comment only lines .........." <<
counters[1] << '\n';
std::cout << "Number of code only lines ............." <<
counters[2] << '\n';
std::cout << "Number of lines with code and comments " <<
counters[3] << '\n';
std::cout.setf(std::ios::fixed);
std::cout.precision(1);
std::cout << "Comment to code ratio " <<
float(counters[1] + counters[3]) /
float(counters[2] + counters[3]) * 100.0 << "%\n";
}
};
static line_counter line_count; // Counter of lines
static paren_counter paren_count; // Counter of ( ) levels
static brace_counter brace_count; // Counter of {} levels
static comment_counter comment_count; // Counter of comment info
// A list of the statistics we are collecting
static a_stat *stat_list[] = {
&line_count,
&paren_count,
&brace_count,
&comment_count,
NULL
};
/********************************************************
* do_file -- process a single file *
* *
* Parameters *
* name -- the name of the file to process *
********************************************************/
static void do_file(const char *const name)
{
input_file in_file(name); // File to read
token token; // Token reader/parser
TOKEN_TYPE cur_token; // Current token type
class a_stat **cur_stat; // Pointer to stat for collection/writing
if (in_file.bad( )) {
std::cerr << "Error: Could not open file " <<
name << " for reading\n";
return;
}
while (true) {
cur_token = token.next_token(in_file);
for (cur_stat = stat_list; *cur_stat != NULL; ++cur_stat)
(*cur_stat)->take_token(cur_token);
#ifdef DEBUG
assert(cur_token >= 0);
assert(cur_token < sizeof(TOKEN_NAMES)/sizeof(TOKEN_NAMES[0]));
std::cout << " " << TOKEN_NAMES[cur_token] << '\n';
#endif /* DEBUG */
switch (cur_token) {
case T_NEWLINE:
for (cur_stat = stat_list; *cur_stat != NULL; ++cur_stat)
(*cur_stat)->line_start( );
in_file.flush_line( );
break;
case T_EOF:
for (cur_stat = stat_list; *cur_stat != NULL; ++cur_stat)
(*cur_stat)->eof( );
return;
default:
// Do nothing
break;
}
}
}
int main(int argc, char *argv[])
{
char *prog_name = argv[0]; // Name of the program
if (argc == 1) {
std::cerr << "Usage is " << prog_name << "[options] <file-list>\n";
exit (8);
}
for (/* argc set */; argc > 1; --argc) {
do_file(argv[1]);
++argv;
}
return (0);
}
Example 27-8. stat/makefile.unx
#
# Makefile for many Unix compilers using the
# "standard" command name CC
#
CC=CC
CFLAGS=-g
OBJS= stat.o ch_type.o token.o
all: stat.out stat
stat.out: stat
stat ../calc3/calc3.cpp >stat.out
stat: $(OBJS)
$(CC) $(CCFLAGS) -o stat $(OBJS)
stat.o: stat.cpp token.h
$(CC) $(CCFLAGS) -c stat.cpp
ch_type.o: ch_type.cpp ch_type.h
$(CC) $(CCFLAGS) -c ch_type.cpp
token.o: token.cpp token.h ch_type.h
$(CC) $(CCFLAGS) -c token.cpp
clean:
rm stat stat.o ch_type.o token.o
Example 27-9. stat/makefile.gnu
#
# Makefile for the Free Software Foundations g++ compiler
#
CC=g++
CCFLAGS=-g -Wall
OBJS= stat.o ch_type.o token.o
all: stat.out stat
stat.out: stat
stat ../calc3/calc3.cpp >stat.out
stat: $(OBJS)
$(CC) $(CCFLAGS) -o stat $(OBJS)
stat.o: stat.cpp token.h
$(CC) $(CCFLAGS) -c stat.cpp
ch_type.o: ch_type.cpp ch_type.h
$(CC) $(CCFLAGS) -c ch_type.cpp
token.o: token.cpp token.h ch_type.h
$(CC) $(CCFLAGS) -c token.cpp
clean:
rm stat stat.o ch_type.o token.o
Example 27-10. stat/makefile.bcc
#
# Makefile for Borland's Borland-C++ compiler
#
CC=bcc32
#
# Flags
# -N -- Check for stack overflow
# -v -- Enable debugging
# -w -- Turn on all warnings
# -tWC -- Console application
#
CFLAGS=-N -v -w -tWC
OBJS= stat.obj ch_type.obj token.obj
all: stat.out stat.exe
stat.out: stat.exe
stat ..\calc3\calc3.cpp >stat.out
stat.exe: $(OBJS)
$(CC) $(CCFLAGS) -estat $(OBJS)
stat.obj: stat.cpp token.h
$(CC) $(CCFLAGS) -c stat.cpp
ch_type.obj: ch_type.cpp ch_type.h
$(CC) $(CCFLAGS) -c ch_type.cpp
token.obj: token.cpp token.h ch_type.h
$(CC) $(CCFLAGS) -c token.cpp
clean:
erase stat.exe stat.obj ch_type.obj token.obj
Example 27-11. stat/makefile.msc
#
# Makefile for Microsoft Visual C++
#
CC=cl
#
# Flags
# AL -- Compile for large model
# Zi -- Enable debugging
# W1 -- Turn on warnings
#
CFLAGS=/AL /Zi /W1
OBJS= stat.obj ch_type.obj token.obj
all: stat.out stat.exe
stat.out: stat.exe
stat ..\calc3\calc3.cpp >stat.out
stat.exe: $(OBJS)
$(CC) $(CCFLAGS) $(OBJS)
stat.obj: stat.cpp token.h
$(CC) $(CCFLAGS) -c stat.cpp
ch_type.obj: ch_type.cpp ch_type.h
$(CC) $(CCFLAGS) -c ch_type.cpp
token.obj: token.cpp token.h ch_type.h
$(CC) $(CCFLAGS) -c token.cpp
clean:
erase stat.exe stat.obj ch_type.obj token.obj
|