| The Personal Software Process: an Independent Study | ||
|---|---|---|
| Prev | Chapter 3. Lesson 3: Planning: Estimating software Size | Next |
Write a program to count total program LOC, the total LOC in each class the progra contains, and the number of methods in each class.
Requirements: Produce a single LOC count for an entire source program file and separate LOC and method counts for each [class]. Print out each [class] name together with its LOC and method count. Also print out the total program LOC count... Use the counting standard produced by report exercise R1. It is acceptable to enhance program 2A or to reuse some of its methods, procedures, or functions in developing program 3a. Testing: Thoroughly test the program. At a minimum, test the program by counting the total program and [class] LOC in [programs 1A, 2A, and 3A]. Include in your test report a table giving the counts obtained with program 2A and 3A for all the programs wirtten to date. | ||
| --From [Humphrey95] | ||
Table 3-1. Test Results Format -- Program 3A
| Program Number | [Class] Name | Number of Methods | [Class] LOC | Total Program LOC |
| 1A | ABC | 3 | 86 | |
| DEF | 2 | 8 | ||
| GHI | 4 | 92 | ||
| 212 | ||||
| 2A | ... |
The phrasing of some of the requirements somewhat implies that the program is expecting one source file for the entire program; our coding standard dictates that a separate source code file be used for each class. This can be easily dealt with by keeping our philosophy of parsing standard input, and funneling all files into the program using a unix pipe (see the requirements for program 2a for an explanation).
Of course, since we have decided in the coding standards to split each class into a separate source file, it's tempting to use the trivial implementation-- run program 2a once per source file to count the LOC in a class! However, this not only means more work in usage, but also defeats the purpose of writing another program using PSP 0.1. As a result, we'll parse the entire program on standard input. No checking for validity will be done. LOC will be counted as it was in program 2a.
For C++ code, code in both the class header and code for each feature in its implementation will be counted; number of features will be based on both code features (methods) and data features (variables). The "number of features" count for C++ code will be based on code in the class header.
For Eiffel code, all code is included in the requisite class header, so class data will be taken from only the code listed there.
No effort will be made to count the LOC for individual methods; for example, a class with methods A and B might produce a total LOC count of 18 and a "number of methods" count of 2, but would not know that method A had 10 LOC and B had 8.
The C++ code for program 2A came to 233 LOC. I can reuse much of that, adding only minor features to the simple_loc_counter class to add feature count and loc/class count. Without any other data, I'll be making a very rough guess of 64 new/changed LOC.
Again, I have little data to extrapolate from. Program 2A took me quite a bit of time, about 214 minutes, so I'll guess about 1/2 that time to add the other features; I'll say about 107 minutes. I've used the % to date entries from the last planning sheet to plan time for each section here; the results can be found in the project plan summary, in the postmortem.
Program 3a will extend program 2a by declaring a new class, class_based_loc_counter, inheriting from simple_loc_counter. It will track number of features in each method by counting semicolons in the class declaration (in case feature declarations flow over line boundaries) and count LOC in methods and classes by tracking block begin/end levels. Block begin/end levels will be tracked by an integer counter, much the same as comment begin/end levels were tracked in program 2a.
I will do minor modifications to the original program 2a code. The simple_loc_counter class will have a minor refactoring, breaking the block begin/end checks into a begin check and end check; block nesting levels will be added to that class. The main program will require very minor changes, simply changing the class of the loc_counter object.
Holy smoke-- this took a great deal longer than I expected, mostly due to the vagarieties of the C++ language.
/*
*/
#ifndef SIMPLE_INPUT_PARSER_H
#define SIMPLE_INPUT_PARSER_H
#include <string>
#include <iostream>
//a standard "framework" for parsing a set of input lines
class simple_input_parser
{
public:
//sets input stream
void set_input_stream (istream * new_input);
//parse input until EOF
void parse_until_eof (void);
//reads single line from input, transforms it, stores it in last_line
void read_line (void);
//sets the last line read
void set_last_line (const std::string & new_line);
//last line read from input
const std::string & last_line (void) const;
//parses the last line read
virtual void parse_last_line (void);
//returns a "transformed" copy of the given line
virtual std::string transformed_line (const std::string & line) const;
//constructor
simple_input_parser (void);
//virtual destructor
virtual ~ simple_input_parser (void);
//resets the parser
virtual void reset (void);
private:
//the input stream
istream * m_input_stream;
//the last line of input
std::string m_last_line;
};
#endif
/*
*/ |
/*
*/
#include "simple_input_parser.h"
#ifndef CONTRACT_H
#include "contract.h"
#endif
void
simple_input_parser::set_input_stream (istream * new_input)
{
m_input_stream = new_input;
}
void
simple_input_parser::parse_until_eof (void)
{
REQUIRE (m_input_stream != NULL);
while (!(m_input_stream->eof ()))
{
read_line ();
parse_last_line ();
}
}
void
simple_input_parser::read_line (void)
{
REQUIRE (!(m_input_stream->eof ()));
const int
input_buffer_size = 255;
char
input_buffer[input_buffer_size];
m_input_stream->getline (input_buffer, input_buffer_size);
//for some reason, the G++ standard library needs me to do this or it
//doesn't register the EOF condition properly. This makes no sense
//to me...
char c = m_input_stream->get();
m_input_stream->putback( c );
std::string input_line (input_buffer);
//no source code line should be longer than 255!
CHECK (input_line.size () < 255);
set_last_line (transformed_line (input_line));
}
void
simple_input_parser::set_last_line (const std::string & new_line)
{
m_last_line = new_line;
}
const
std::string & simple_input_parser::last_line (void) const
{
return m_last_line;
}
void
simple_input_parser::parse_last_line (void)
{
//basic version does nothing
}
std::string simple_input_parser::transformed_line (const std::string & line) const
{
return line;
}
simple_input_parser::simple_input_parser (void)
{
reset ();
}
simple_input_parser::~simple_input_parser (void)
{
}
void
simple_input_parser::reset (void)
{
m_input_stream = NULL;
m_last_line = "";
}
/*
*/ |
/*
*/
#ifndef SIMPLE_LOC_COUNTER_H
#define SIMPLE_LOC_COUNTER_H
#ifndef SIMPLE_INPUT_PARSER_H
#include "simple_input_parser.h"
#endif
#include <string>
#include <vector>
//subclass of simple_input_parser that stores countable lines of code in a buffer
//and can return their count.
class simple_loc_counter:public simple_input_parser
{
public:
//adds last line to the buffered lines if it is countable
void parse_last_line (void);
//the count of LOC
int loc_count (void) const;
//whether last line was comment
bool last_line_is_comment (void) const;
//whether last line was compiler directive
bool last_line_is_compiler_directive (void) const;
//whether we are in a block comment
bool is_in_block_comment (void) const;
//whether last line was a block begin
bool last_line_is_block_begin (void) const;
//whether last line was a block end
bool last_line_is_block_end (void) const;
//whether last line was part of a begin/end pair
bool last_line_is_block_begin_or_end (void) const;
//whether the last line was countable
bool last_line_is_countable (void) const;
//whether the last line was empty
bool last_line_is_empty (void) const;
//updates the block comment count
void update_block_comment_count (void);
//updates the block nesting level
void update_block_nesting_level (void);
//are we in a block?
bool is_in_block (void) const;
//block nesting level
int block_nesting_level (void) const;
//whether the last line starts with the given string
bool last_line_starts_with (const std::string & search_string) const;
//whether a given string starts with a search string
static bool string_starts_with (const std::string & given_string,
const std::string & search_string);
//returns the input string stripped of leading/trailing whitespace
std::string string_stripped_of_whitespace (const std::string & input_string) const;
//returns the transformed line (here, the line stripped of whitespace)
virtual std::
string transformed_line (const std::string & input_string) const;
//constructor
simple_loc_counter (void);
//destructor
virtual ~ simple_loc_counter (void);
//resets the object
virtual void reset (void);
//writes the countable lines to the given output stream
void write_countable_lines (ostream & ostr) const;
protected:
//the buffered countable lines
std::vector < std::string > m_countable_lines;
//the "block comment" nesting level
int m_block_comment_nesting_level;
//the "block" nesting level
int m_block_nesting_level;
//the beginning of a block comment
static const std::string & block_comment_begin;
//the end of a block comment
static const std::string & block_comment_end;
//the beginning of an inline comment
static const std::string & inline_comment_begin;
//the beginning of a compiler directive
static const std::string & compiler_directive_begin;
//the "begin block" string
static const std::string & block_begin;
//the "end block" string
static const std::string & block_end;
//whitespace characters
static const std::string & whitespace_characters;
};
#endif
/*
*/ |
/*
*/
#include "simple_loc_counter.h"
#ifndef YAK_MIN_MAX_H
#include "yak_min_max.h"
#endif
#ifndef CONTRACT_H
#include "contract.h"
#endif
void
simple_loc_counter::parse_last_line (void)
{
if (last_line_is_countable ())
{
m_countable_lines.push_back (last_line ());
}
update_block_comment_count ();
update_block_nesting_level ();
}
int
simple_loc_counter::loc_count (void) const
{
return m_countable_lines.size ();
}
bool
simple_loc_counter::last_line_is_comment (void) const
{
bool Result = false;
if (last_line_starts_with (block_comment_begin)
|| last_line_starts_with (inline_comment_begin)
|| is_in_block_comment ())
{
Result = true;
}
return Result;
}
bool
simple_loc_counter::last_line_is_compiler_directive (void) const
{
bool Result = false;
if (last_line_starts_with (compiler_directive_begin))
{
Result = true;
}
return Result;
}
bool
simple_loc_counter::is_in_block_comment (void) const
{
bool Result = false;
if (m_block_comment_nesting_level > 0)
{
Result = true;
}
return Result;
}
bool
simple_loc_counter::last_line_is_block_begin (void) const
{
bool
Result = false;
if (last_line_starts_with (block_begin))
{
Result = true;
}
return Result;
}
bool
simple_loc_counter::last_line_is_block_end (void) const
{
bool
Result = false;
if (last_line_starts_with (block_end))
{
Result = true;
}
return Result;
}
bool
simple_loc_counter::last_line_is_block_begin_or_end (void) const
{
return (last_line_is_block_begin () | last_line_is_block_end ());
}
bool
simple_loc_counter::last_line_is_empty (void) const
{
return (last_line ().length () == 0);
}
bool
simple_loc_counter::last_line_is_countable (void) const
{
bool Result = true;
if ((last_line_is_comment ())
|| (last_line_is_block_begin_or_end ())
|| (last_line_is_compiler_directive ()) || (last_line_is_empty ()))
{
Result = false;
}
return Result;
}
void
simple_loc_counter::update_block_comment_count (void)
{
//count through the string; add 1 to the block comment count if the begin
//string is encountered, subtract one if the end string is encountered.
for (unsigned int i = 0; i < last_line ().length (); ++i)
{
std::string line_remaining =
last_line ().substr (i, last_line ().length ());
if (string_starts_with (line_remaining, block_comment_begin))
{
++m_block_comment_nesting_level;
}
else if (string_starts_with (line_remaining, block_comment_end))
{
--m_block_comment_nesting_level;
}
}
}
void
simple_loc_counter::update_block_nesting_level (void)
{
if (last_line_is_block_begin ())
{
++m_block_nesting_level;
}
else if (last_line_is_block_end ())
{
--m_block_nesting_level;
}
ENSURE (m_block_nesting_level >= 0);
}
bool simple_loc_counter::is_in_block (void) const
{
return (m_block_nesting_level > 0);
}
int
simple_loc_counter::block_nesting_level (void) const
{
return m_block_nesting_level;
}
bool
simple_loc_counter::
last_line_starts_with (const std::string & search_string) const
{
return string_starts_with (last_line (), search_string);
}
bool
simple_loc_counter::string_starts_with (const std::string & given_string,
const std::string & search_string)
{
int
substring_size =
yak_min (given_string.length (), search_string.length ());
std::string substring = given_string.substr (0, substring_size);
bool Result = (substring == search_string);
return Result;
}
std::string
simple_loc_counter::string_stripped_of_whitespace (const std::
string & input_string) const
{
std::string::size_type start =
input_string.find_first_not_of (whitespace_characters);
if (start == input_string.npos)
{
start = 0;
}
std::string::size_type end =
input_string.find_last_not_of (whitespace_characters);
if (end == input_string.npos)
{
end = input_string.length ();
}
if (end < input_string.length ())
{
++end;
}
std::string Result = input_string.substr (start, end);
return Result;
}
std::string
simple_loc_counter::transformed_line (const std::string & input_string) const
{
return string_stripped_of_whitespace (input_string);
}
simple_loc_counter::simple_loc_counter (void)
{
reset ();
}
simple_loc_counter::~simple_loc_counter (void)
{
}
void
simple_loc_counter::reset (void)
{
m_countable_lines.clear ();
m_block_comment_nesting_level = 0;
m_block_nesting_level = 0;
}
void
simple_loc_counter::write_countable_lines (ostream & ostr) const
{
for (std::vector < std::string >::const_iterator iter =
m_countable_lines.begin (); iter != m_countable_lines.end (); ++iter)
{
ostr << *iter << "\n";
}
}
const
std::string & simple_loc_counter::block_comment_begin = std::string( "/" ) + std::string ("*");
const
std::string & simple_loc_counter::block_comment_end = std::string( "*" ) + std::string( "/" );
const
std::string & simple_loc_counter::inline_comment_begin = "//";
const
std::string & simple_loc_counter::compiler_directive_begin = "#";
const
std::string & simple_loc_counter::block_begin = "{";
const
std::string & simple_loc_counter::block_end = "}";
const
std::string & simple_loc_counter::whitespace_characters = " \t\n\0x32";
/*
*/ |
/*
*/
#ifndef CLASS_METRIC_H
#define CLASS_METRIC_H
class class_metric
{
public:
//LOC in the class
int loc;
//number of features
int feature_count;
class_metric (void);
};
#endif
/*
*/ |
/*
*/
#include "class_metric.h"
class_metric::class_metric (void):
loc (0), feature_count (0)
{
}
/*
*/ |
/*
*/
#ifndef CLASS_BASED_LOC_COUNTER_H
#define CLASS_BASED_LOC_COUNTER_H
#ifndef SIMPLE_LOC_COUNTER_H
#include "simple_loc_counter.h"
#endif
#ifndef CLASS_METRIC_H
#include "class_metric.h"
#endif
#include <map>
class class_based_loc_counter:public simple_loc_counter
{
protected:
//mapping of class names to metrics
std::map < std::string, class_metric > m_class_map;
//are we in a class declaration?
bool m_in_class_declaration;
//are we in a class feature implementation?
bool m_in_class_feature;
//name of the class we're in
std::string m_current_class_name;
static const std::string class_begin;
static const std::string scope_operator;
std::string m_previous_line;
public:
//adds the last line to method/class counts as appropriate
virtual void parse_last_line (void);
std::string class_name_from_class_begin (const std::string & str) const;
std::string class_name_from_external_feature (const std::string & str) const;
bool last_line_is_class_begin (void) const;
bool last_line_is_class_end (void) const;
bool last_line_is_feature_begin (void) const;
bool last_line_is_feature_end (void) const;
bool last_line_is_data_feature (void) const;
bool last_line_declares_feature (void) const;
bool last_line_contains_scope_operator (void) const;
bool last_line_contains_known_class_before_scope_operator (void) const;
void adjust_class_feature_count (const std::string & class_name,
int adjustment);
void adjust_class_loc_count (const std::string & class_name,
int adjustment);
bool has_entry_for_class_name (const std::string & class_name) const;
const std::map < std::string, class_metric > &class_map (void) const;
class_based_loc_counter (void);
virtual void reset (void);
};
#endif
/*
*/ |
/*
*/
#include "class_based_loc_counter.h"
#ifndef CONTRACT_H
#include "contract.h"
#endif
const
std::string class_based_loc_counter::class_begin ("class ");
const
std::string class_based_loc_counter::scope_operator ("::");
class_based_loc_counter::class_based_loc_counter (void):
simple_loc_counter ()
{
reset ();
}
void
class_based_loc_counter::reset (void)
{
simple_loc_counter::reset();
m_class_map.clear ();
m_in_class_declaration = false;
m_in_class_feature = false;
m_current_class_name = "UNSET";
m_previous_line = "";
}
void
class_based_loc_counter::parse_last_line (void)
{
simple_loc_counter::parse_last_line ();
//cout << m_class_map[ "class_based_loc_counter" ].loc << ":" << m_countable_lines.size() << ":" << last_line() << "\n";
if (last_line_is_countable ())
{
if (last_line_is_class_begin ())
{
CHECK (m_in_class_declaration == false);
m_in_class_declaration = true;
m_current_class_name = class_name_from_class_begin (last_line ());
}
if (m_in_class_declaration)
{
adjust_class_loc_count (m_current_class_name, 1);
if (last_line_declares_feature ())
{
adjust_class_feature_count (m_current_class_name, 1);
}
}
if (last_line_is_feature_begin ())
{
//cout << "<<begin feature; previous line = " << m_previous_line << ">>";
CHECK (m_in_class_feature == false);
m_in_class_feature = true;
m_current_class_name =
class_name_from_external_feature (last_line ());
//add one for the return type declaration, as indent usu puts it on the previous line
if ((m_previous_line.length () > 0))
{
//cout << "*" << m_previous_line;
adjust_class_loc_count (m_current_class_name, 1);
}
}
if (m_in_class_feature)
{
adjust_class_loc_count (m_current_class_name, 1);
}
if (last_line_is_data_feature ())
{
adjust_class_loc_count (class_name_from_external_feature
(last_line ()), 1);
if (m_previous_line.length () > 0)
{
//cout << "&" << m_previous_line;
adjust_class_loc_count (m_current_class_name, 1);
}
}
}
if (last_line_is_class_end ())
{
CHECK (block_nesting_level () == 0);
CHECK (m_in_class_declaration == true);
CHECK (m_in_class_feature == false);
m_in_class_declaration = false;
}
if (last_line_is_feature_end ())
{
//cout << "<<end feature, nesting:" << block_nesting_level() << ">>";
CHECK (block_nesting_level () == 0);
CHECK (m_in_class_declaration == false);
CHECK (m_in_class_feature == true);
m_in_class_feature = false;
}
m_previous_line = last_line ();
}
std::string
class_based_loc_counter::
class_name_from_class_begin (const std::string & str) const
{
REQUIRE (last_line_is_class_begin ());
std::string::size_type class_begin_position =
last_line ().find (class_begin);
CHECK (class_begin_position != last_line ().npos);
std::string::size_type next_white_space_position =
last_line ().find_first_of (whitespace_characters, class_begin_position);
CHECK (class_begin_position != last_line ().npos);
std::string::size_type next_word_position =
last_line ().find_first_not_of (whitespace_characters,
next_white_space_position);
CHECK (next_word_position != last_line ().npos);
std::string whitespace_characters_and_colon = whitespace_characters;
whitespace_characters_and_colon.append (":");
std::string::size_type end_of_class_name_position =
last_line ().find_first_of (whitespace_characters_and_colon,
next_word_position);
if (end_of_class_name_position == last_line ().npos)
{
end_of_class_name_position = last_line ().length ();
}
std::string Result =
last_line ().substr (next_word_position,
end_of_class_name_position - next_word_position);
//cout << Result;
return Result;
}
std::string
class_based_loc_counter::
class_name_from_external_feature (const std::string & str) const
{
//this is used by last_line_is_feature_begin, so we can't use it here as
//a requirement... be cautious!
std::string::size_type scope_operator_position = last_line ().length ();
while (scope_operator_position != last_line ().npos)
{
scope_operator_position =
last_line ().rfind (scope_operator, scope_operator_position - 1);
if (scope_operator_position != last_line ().npos);
{
std::string beginning_of_string =
last_line ().substr (0, scope_operator_position);
//cout << "<<" << beginning_of_string << ">>";
std::string::size_type begin =
beginning_of_string.find_last_of (whitespace_characters);
if (begin == beginning_of_string.npos)
{
begin = 0;
}
else
{
begin = begin + 1;
}
std::string possible_result =
beginning_of_string.substr (begin, scope_operator_position);
//cout << "POSS:" << possible_result;
if (has_entry_for_class_name (possible_result))
{
return possible_result;
}
}
}
return "";
}
bool
class_based_loc_counter::last_line_is_class_begin (void) const
{
bool Result = false;
if (last_line_starts_with (class_begin))
{
Result = true;
}
return Result;
}
bool
class_based_loc_counter::last_line_is_class_end (void) const
{
bool Result = false;
if (last_line_is_block_end () && m_in_class_declaration)
{
Result = true;
}
return Result;
}
bool
class_based_loc_counter::last_line_contains_scope_operator (void) const
{
return (last_line ().find (scope_operator) != last_line ().npos);
}
bool
class_based_loc_counter::last_line_contains_known_class_before_scope_operator
(void) const
{
bool Result = false;
if (last_line_contains_scope_operator ())
{
std::string class_name =
class_name_from_external_feature (last_line ());
if (has_entry_for_class_name (class_name))
{
Result = true;
}
}
return Result;
}
bool
class_based_loc_counter::last_line_is_feature_begin (void) const
{
bool Result = false;
if (last_line_contains_known_class_before_scope_operator ())
{
if (!last_line_is_data_feature ())
{
Result = true;
}
}
return Result;
}
bool
class_based_loc_counter::last_line_is_feature_end (void) const
{
bool Result = false;
if (last_line_is_block_end ()
&& m_in_class_feature && (block_nesting_level () == 0))
{
Result = true;
}
return Result;
}
bool
class_based_loc_counter::last_line_is_data_feature (void) const
{
bool Result = false;
if (last_line_contains_known_class_before_scope_operator ())
{
if (last_line ().find (";") != last_line ().npos)
{
Result = true;
}
}
return Result;
}
bool
class_based_loc_counter::last_line_declares_feature (void) const
{
bool Result = false;
if (m_in_class_declaration
&& (last_line ().find (";") != last_line ().npos))
{
Result = true;
}
return Result;
}
bool
class_based_loc_counter::has_entry_for_class_name (const yak_string &
class_name) const
{
bool Result = false;
if (class_map ().find (class_name) != class_map ().end ())
{
Result = true;
}
return Result;
}
void
class_based_loc_counter::
adjust_class_feature_count (const std::string & class_name, int adjustment)
{
if (!has_entry_for_class_name (class_name))
{
m_class_map[class_name] = class_metric ();
}
m_class_map[class_name].feature_count += adjustment;
}
void
class_based_loc_counter::
adjust_class_loc_count (const std::string & class_name, int adjustment)
{
if (!has_entry_for_class_name (class_name))
{
m_class_map[class_name] = class_metric ();
}
m_class_map[class_name].loc += adjustment;
}
const
std::map < std::string,
class_metric > & class_based_loc_counter::class_map (void) const
{
return m_class_map;
}
/*
*/ |
/*
*/
#ifndef CLASS_BASED_LOC_COUNTER_H
#include "class_based_loc_counter.h"
#endif
#ifndef YAK_EXCEPTION_H
#include "yak_exception.h"
#endif
istream *
input_stream_from_args (int arg_count, const char **arg_vector)
{
istream *Result = NULL;
if (arg_count == 1)
{
Result = &cin;
}
else
{
const char *help_text = "PSP exercise 3A: Count class and total LOC.\n Usage:\n\tpsp_3a\n\n ";
cout << help_text;
}
return Result;
}
int
main (int arg_count, const char **arg_vector)
{
//get the input stream, or print the help text as appropriate
istream *input_stream = input_stream_from_args (arg_count, arg_vector);
if (input_stream != NULL)
{
class_based_loc_counter counter;
try
{
counter.set_input_stream (input_stream);
counter.parse_until_eof ();
//output the counted lines
//counter.write_countable_lines( cout );
//output the loc
cout << "Total LOC: " << counter.loc_count () << "\n";
cout << "classname:feature count:LOC by class:\n";
for (std::map < std::string, class_metric >::const_iterator iter =
counter.class_map ().begin ();
iter != counter.class_map ().end (); ++iter)
{
cout << iter->first << ":"
<< iter->second.feature_count << ":"
<< iter->second.loc << "\n";
}
}
catch (exception & e)
{
cout << "Aborted with exception: " << e.what () << "\n";
cout << "last line parsed: \n" << counter.last_line () << "\n";
}
}
}
/*
*/ |
deferred class SIMPLE_INPUT_PARSER
feature {ANY}
parse_until_eof is
--parses all input until an EOF is reached
require
input_stream /= Void;
do
from
until
input_stream.end_of_input
loop
read_line;
if not input_stream.end_of_input then
parse_last_line;
end;
end;
end -- parse_until_eof
set_input(new_input_stream: INPUT_STREAM) is
--sets the input stream
do
input_stream := new_input_stream;
end -- set_input
read_line is
--reads a line from standard input
do
input_stream.read_line;
last_line := transformed_line(input_stream.last_string);
end -- read_line
last_line: STRING;
input_stream: INPUT_STREAM;
parse_last_line is
deferred
end -- parse_last_line
transformed_line(to_transform: STRING): STRING is
--transforms the line according to rules defined in subclasses
do
Result := to_transform;
end -- transformed_line
end -- class SIMPLE_INPUT_PARSER |
class SIMPLE_LOC_COUNTER
--counts one form of LOC in eiffel files
inherit
SIMPLE_INPUT_PARSER
redefine parse_last_line, transformed_line
end;
creation {ANY}
make
feature {ANY}
make is
do
!!counted_lines.make(1,0);
block_nesting_level := 0;
end -- make
parse_last_line is
--store countable lines in an array
do
update_block_nesting_level;
-- std_output.put_integer( block_nesting_level )
-- std_output.put_string( ":" + last_line + "%N" )
if last_line_is_countable then
counted_lines.add_last(last_line);
end;
end -- parse_last_line
counted_lines: ARRAY[STRING];
--array containing countable lines
block_nesting_level: INTEGER;
--nesting level of syntactic blocks
update_block_nesting_level is
-- update the nesting level of syntactic blocks according to
-- the current line
do
if last_line_is_block_begin then
block_nesting_level := block_nesting_level + 1;
end;
if last_line_is_block_end then
block_nesting_level := block_nesting_level - 1;
end;
ensure
block_nesting_level >= 0;
end -- update_block_nesting_level
loc_count: INTEGER is
--number of lines counted as LOC
do
Result := counted_lines.count;
end -- loc_count
last_line_is_comment: BOOLEAN is
do
if last_line_starts_with(comment_begin) then
Result := true;
else
Result := false;
end;
end -- last_line_is_comment
comment_begin: STRING is "--";
last_line_is_compiler_directive: BOOLEAN is false;
in_block_comment: BOOLEAN is false;
last_line_starts_with(test_string: STRING): BOOLEAN is
do
if last_line.has_prefix(test_string) then
Result := true;
else
Result := false;
end;
end -- last_line_starts_with
last_line_ends_with(test_string: STRING): BOOLEAN is
do
if last_line.has_suffix(test_string) then
Result := true;
else
Result := false;
end;
end -- last_line_ends_with
last_line_is_countable: BOOLEAN is
do
if last_line_is_comment or last_line_is_block_begin_or_end or last_line_is_empty then
Result := false;
else
Result := true;
end;
end -- last_line_is_countable
last_line_starts_with_any_of(strings: ARRAY[STRING]): BOOLEAN is
local
index: INTEGER;
do
Result := false;
from
index := strings.lower;
until
index > strings.upper
loop
if last_line_starts_with(strings.item(index)) then
Result := true;
end;
index := index + 1;
end;
end -- last_line_starts_with_any_of
block_beginners: ARRAY[STRING] is
once
Result := <<"do","if","from","class ","redefine","check","deferred","once">>;
end -- block_beginners
last_line_is_block_begin: BOOLEAN is
do
if last_line_starts_with_any_of(block_beginners) then
Result := true;
else
Result := false;
end;
end -- last_line_is_block_begin
last_line_is_block_end: BOOLEAN is
do
if last_line_starts_with("end") then
Result := true;
else
Result := false;
end;
end -- last_line_is_block_end
last_line_is_block_begin_or_end: BOOLEAN is
do
if last_line_is_block_begin or last_line_is_block_end then
Result := true;
else
Result := false;
end;
end -- last_line_is_block_begin_or_end
last_line_is_empty: BOOLEAN is
do
if last_line.empty then
Result := true;
else
Result := false;
end;
end -- last_line_is_empty
transformed_line(string: STRING): STRING is
do
Result := string_stripped_of_whitespace(string);
end -- transformed_line
string_stripped_of_whitespace(string: STRING): STRING is
do
Result := string.twin;
Result.replace_all('%T',' ');
Result.left_adjust;
Result.right_adjust;
end -- string_stripped_of_whitespace
print_counted_lines(output: OUTPUT_STREAM) is
local
index: INTEGER;
do
from
index := counted_lines.lower;
until
not counted_lines.valid_index(index)
loop
output.put_string(counted_lines.item(index));
output.put_string("%N");
index := index + 1;
end;
end -- print_counted_lines
end -- class SIMPLE_LOC_COUNTER |
class CLASS_METRIC
creation {ANY}
make
feature {ANY}
loc: INTEGER;
--lines of code for this class
feature_count: INTEGER;
--number of features in this class
adjust_loc(adjustment: INTEGER) is
--adjusts loc by given amount
do
loc := loc + adjustment;
end -- adjust_loc
adjust_feature_count(adjustment: INTEGER) is
--adjusts feature count by given amount
do
feature_count := feature_count + adjustment;
end -- adjust_feature_count
make is
do
loc := 0;
feature_count := 0;
end -- make
end -- CLASS_METRIC |
class CLASS_BASED_LOC_COUNTER
--counts one form of LOC in eiffel files, giving a count of
--LOC/class and number of features/class
inherit
SIMPLE_LOC_COUNTER
redefine parse_last_line, make
end;
creation {ANY}
make
feature {ANY}
class_map: DICTIONARY[CLASS_METRIC,STRING];
in_class: BOOLEAN;
current_class_name: STRING;
class_beginners: ARRAY[STRING] is
once
Result := <<"class ","deferred class ">>;
end -- class_beginners
last_line_is_class_begin: BOOLEAN is
--whether last line starts a class declaration
do
if last_line_starts_with_any_of(class_beginners) then
Result := true;
else
Result := false;
end;
end -- last_line_is_class_begin
last_line_is_class_end: BOOLEAN is
--whether last line ends class declaration
do
if last_line_is_block_end and block_nesting_level = 0 then
Result := true;
else
Result := false;
end;
end -- last_line_is_class_end
last_line_declares_feature: BOOLEAN is
--does last line begin new feature?
do
Result := false;
-- if last_line_ends_with( "is" ) and ( block_nesting_level = 1 ) then
-- Result := true
-- else
-- Result :=false
-- end
if in_class and block_nesting_level = 1 and not last_line_declares_feature_clause and not in_local_clause then
if last_line_is_countable and (last_line.has_string(" is") or last_line.has_string(":")) then
Result := true;
end;
end;
end -- last_line_declares_feature
last_line_declares_feature_clause: BOOLEAN is
do
if last_line_starts_with("feature ") then
Result := true;
else
Result := false;
end;
end -- last_line_declares_feature_clause
feature_clause_encountered: BOOLEAN;
blank_metric_entry: CLASS_METRIC is
once
!!Result.make;
end -- blank_metric_entry
ensure_class_entry(name: STRING) is
--ensure that class_map has an entry for name
do
if not class_map.has(name) then
class_map.put(blank_metric_entry.twin,name);
end;
end -- ensure_class_entry
adjust_class_feature_count(name: STRING; adjustment: INTEGER) is
--adjust method count of class metric entry
do
ensure_class_entry(name);
class_map.at(name).adjust_feature_count(adjustment);
end -- adjust_class_feature_count
adjust_class_loc(name: STRING; adjustment: INTEGER) is
--adjust loc of class metric entry
do
ensure_class_entry(name);
class_map.at(name).adjust_loc(adjustment);
end -- adjust_class_loc
make is
do
Precursor;
!!class_map.make;
in_class := false;
feature_clause_encountered := false;
in_local_clause := false;
end -- make
adjust_in_class is
--adjust whether or not we're in a class
do
if last_line_is_class_begin then
check
in_class = false;
block_nesting_level = 1;
end;
in_class := true;
current_class_name := class_name_from_last_line;
end;
if last_line_is_class_end then
check
in_class = true;
block_nesting_level = 0;
end;
in_class := false;
end;
ensure
in_class implies block_nesting_level > 0;
end -- adjust_in_class
class_name_from_last_line: STRING is
--get the class declaration from last input line
require
last_line_is_class_begin;
local
split_line: ARRAY[STRING];
do
split_line := last_line.split;
Result := split_line.item(split_line.upper);
ensure
Result /= Void;
end -- class_name_from_last_line
adjust_feature_clause_encountered is
do
if in_class and last_line_declares_feature_clause then
feature_clause_encountered := true;
end;
if not in_class then
feature_clause_encountered := false;
end;
end -- adjust_feature_clause_encountered
in_local_clause: BOOLEAN;
adjust_in_local_clause is
do
if last_line_starts_with("local") then
in_local_clause := true;
end;
if last_line_is_block_begin then
in_local_clause := false;
end;
end -- adjust_in_local_clause
parse_last_line is
--adjust class count after precursor
do
Precursor;
adjust_in_class;
adjust_feature_clause_encountered;
adjust_in_local_clause;
if in_class and last_line_is_countable then
adjust_class_loc(current_class_name,1);
end;
if in_class and last_line_declares_feature and feature_clause_encountered then
adjust_class_feature_count(current_class_name,1);
-- std_output.put_string( last_line + "%N" )
end;
end -- parse_last_line
end -- class CLASS_BASED_LOC_COUNTER |
class MAIN
creation {ANY}
make
feature {ANY}
make is
local
counter: CLASS_BASED_LOC_COUNTER;
i: INTEGER;
do
!!counter.make;
counter.set_input(io);
counter.parse_until_eof;
std_output.put_string("LOC: ");
std_output.put_integer(counter.loc_count);
std_output.put_string("%Nname:features:loc by class%N");
from
i := counter.class_map.lower
until
i > counter.class_map.upper
loop
std_output.put_string(counter.class_map.key(i));
std_output.put_string(":");
std_output.put_integer(counter.class_map.item(i).feature_count);
std_output.put_string(":");
std_output.put_integer(counter.class_map.item(i).loc);
std_output.put_string("%N");
i := i + 1
end;
-- std_output.put_string( "Block nesting level: " )
-- std_output.put_integer( counter.block_nesting_level )
end -- make
end -- class MAIN |
A few errors in compilation with the C++ code; also noticed a few design errors here, but again most compile errors were pretty minor, having to do with improper syntax, feature names, etc. My unfamiliarity with the Eiffel language generated more errors there.
Oh my. This was a heinous 144-minute session for the C++ code, almost entirely spent on vagarieties of the C++ language (discerning a class name from a feature implemented outside the class declaration while arguments or the return type contain namespaces, dealing with class declarations to list code, etc). There are still a few very minor problems, but frankly I'm willing to keep the code, as the problems generate only about 1-2 LOC difference (applied to total LOC but not class LOC). The Eiffel program took quite a bit longer to test than I'd imagined thanks to Eiffel's asymmetric block constructs (what I'm getting at is that "end" ends a block, but many, many things can begin one, unlike C++'s symmetric "{" and "}". I also worry that my defect recording for the Eiffel program was incomplete as I became distracted by learning how to do things and let my attention slip from the PSP. The results for previous programs are as follows:
Table 3-2. Test Results -- Program 3A, C++
| Program Number | [Class] Name | Number of Methods | [Class] LOC | Total Program LOC |
| 1A | number_list | 7 | 54 | |
| 82 | ||||
| 2A | simple_input_parser | 12 | 52 | |
| simple_loc_counter | 26 | 138 | ||
| 212 | ||||
| 3A | class_based_loc_counter | 24 | 200 | |
| class_metric | 3 | 7 | ||
| simple_input_parser | 12 | 52 | ||
| simple_loc_counter | 32 | 177 | ||
| 463 |
Table 3-3. Test Results -- Program 3A, Eiffel
| Program Number | [Class] Name | Number of Methods | [Class] LOC | Total Program LOC |
| 1A | NUMBER_LIST | 7 | 52 | |
| MAIN | 1 | 18 | ||
| 70 | ||||
| 2A | SIMPLE_INPUT_PARSER | 7 | 19 | |
| SIMPLE_LOC_COUNTER | 15 | 52 | ||
| MAIN | 1 | 11 | ||
| 82 | ||||
| 3A | CLASS_BASED_LOC_COUNTER | 20 | 75 | |
| CLASS_METRIC | 5 | 12 | ||
| SIMPLE_INPUT_PARSER | 7 | 19 | ||
| SIMPLE_LOC_COUNTER | 22 | 84 | ||
| MAIN | 1 | 24 | ||
| 214 |
Table 3-4. Project Plan Summary
| Student: | Victor B. Putz | Date: | 000106 |
| Program: | Class/Feature/LOC counter | Program# | 3A |
| Instructor: | Wells | Language: | C++ |
| Program Size | Plan | Actual | To date |
| Base | 212 | ||
| Deleted | 1 | ||
| Modified | 11 | ||
| Added | 252 | ||
| Reused | 0 | 0 | |
| Total New and Changed | 64 | 263 | 557 |
| Total LOC | 463 | 730 | |
| Total new/reused |
| Time in Phase (min): | Plan | Actual | To Date | To Date% |
| Planning | 8 | 13 | 37 | 6 |
| Design | 15 | 25 | 69 | 11 |
| Code | 32 | 86 | 185 | 29 |
| Compile | 10 | 12 | 43 | 7 |
| Test | 36 | 144 | 254 | 39 |
| Postmortem | 6 | 30 | 50 | 8 |
| Total | 107 | 310 | 638 | 100 |
| Defects Injected | Actual | To Date | To Date % | |
| Plan | 0 | 0 | 0 | |
| Design | 4 | 15 | 23 | |
| Code | 19 | 46 | 70 | |
| Compile | 0 | 2 | 3 | |
| Test | 1 | 3 | 5 | |
| Total development | 24 | 67 | 100 | |
| Defects Removed | Actual | To Date | To Date % | |
| Planning | 0 | 0 | 0 | |
| Design | 0 | 0 | 0 | |
| Code | 4 | 15 | 22 | |
| Compile | 8 | 26 | 39 | |
| Test | 12 | 26 | 39 | |
| Total development | 24 | 67 | 100 | |
| After Development | 0 | 0 |
| Eiffel code/compile/test |
| Time in Phase (min) | Actual | To Date | To Date % |
| Code | 41 | 87 | 40 |
| Compile | 28 | 56 | 26 |
| Test | 61 | 74 | 34 |
| Total | 130 | 217 | 100 |
| Defects Injected | Actual | To Date | To Date % |
| Design | 1 | 4 | 10 |
| Code | 18 | 38 | 90 |
| Compile | 0 | 0 | 0 |
| Test | 0 | 0 | 0 |
| Total | 19 | 42 | 100 |
| Defects Removed | Actual | To Date | To Date % |
| Code | 0 | 1 | 2 |
| Compile | 10 | 24 | 57 |
| Test | 9 | 17 | 41 |
| Total | 19 | 42 | 100 |
Table 3-5. Time Recording Log
| Student: | Victor B. Putz | Date: | 000105 |
| Program: | 3A |
| Start | Stop | Interruption Time | Delta time | Phase | Comments |
| 000105 13:12:10 | 000105 13:38:06 | 12 | 13 | plan | |
| 000105 13:39:55 | 000105 14:05:45 | 0 | 25 | design | |
| 000105 14:13:06 | 000105 15:39:21 | 0 | 86 | code | |
| 000105 15:50:37 | 000105 16:03:28 | 0 | 12 | compile | |
| 000105 16:03:33 | 000105 18:29:20 | 1 | 144 | test | |
| 000105 19:15:59 | 000105 19:46:29 | 0 | 30 | postmortem | |
Table 3-6. Time Recording Log
| Student: | Victor B. Putz | Date: | 000106 |
| Program: | 3A |
| Start | Stop | Interruption Time | Delta time | Phase | Comments |
| 000106 08:42:25 | 000106 09:24:10 | 0 | 41 | code | |
| 000106 09:24:45 | 000106 09:53:24 | 0 | 28 | compile | |
| 000106 09:53:52 | 000106 10:55:43 | 0 | 61 | test | |
Table 3-7. Defect Recording Log
| Student: | Victor B. Putz | Date: | 000105 |
| Program: | 3A |
| Defect found | Type | Reason | Phase Injected | Phase Removed | Fix time | Comments |
| 000105 14:40:22 | md | om | design | code | 9 | forgot to add a feature to extract the class name from the class description |
| 000105 14:55:13 | md | om | design | code | 7 | forgot to add a feature to extract the class name from an external feature |
| 000105 15:21:33 | ic | ig | design | code | 3 | Added some refactorings to find scope operators, etc. |
| 000105 15:29:26 | ic | ig | design | code | 2 | Added "has_entry_for_class_name" feature |
| 000105 15:52:18 | ch | om | code | compile | 0 | Forgot to include required .h file for ENSURE macro |
| 000105 15:55:19 | sy | cm | code | compile | 0 | accidentally included "static" in member implementation for data members |
| 000105 15:56:20 | wn | cm | code | compile | 0 | Used "adjust_class_method_count" instead of "adjust_class_feature_count" |
| 000105 15:57:25 | wn | cm | code | compile | 0 | Wrong name: m_class_name instead of m_current_class_name |
| 000105 15:58:26 | wn | cm | code | compile | 0 | Wrong name: used "method" in name instead of "feature" |
| 000105 15:59:18 | wn | cm | code | compile | 0 | Missed namespace requirement, ie std::string::size_type instead of just size_type |
| 000105 16:00:29 | ic | om | code | compile | 0 | forgot to declare a feature as const |
| 000105 16:01:56 | ic | om | code | compile | 1 | forgot to add class_map accessor function implementation |
| 000105 16:05:06 | me | ex | code | test | 20 | Erroneous contract exception handling going on-- nothing to do with program 3a |
| 000105 16:26:07 | md | ig | code | test | 2 | Added more diagnostic stubs |
| 000105 16:31:15 | mi | om | code | test | 0 | Forgot to reset the counter during construction. Silly, that. |
| 000105 16:32:21 | wn | ig | code | test | 0 | Was looking for "class" at the beginning of class declarations instead of "class " |
| 000105 16:34:14 | wn | cm | code | test | 1 | Used manifest constant "class" instead of class constant for class begin |
| 000105 16:36:26 | we | kn | code | test | 13 | The logic to strip whitespace was screwy, ignoring one-character lines |
| 000105 16:54:59 | wa | kn | code | test | 4 | Due to improper timing of block comment nesting level calculation, end of block comments were "countable" |
| 000105 17:00:01 | wa | kn | code | test | 3 | Added check to make sure that the previous line had something useful on it before adding a line for external feature declaration |
| 000105 17:04:34 | wa | kn | test | test | 38 | Many problems handling namespaces in counting code. Unfortunately, a few errors were fixed without logging here. |
| 000105 17:46:36 | wa | kn | code | test | 8 | Error handling class declaration name extraction when inheritance used. |
| 000105 18:00:05 | ma | om | code | test | 20 | Was treating the quoted "/*" marks as nested comments when they were string data. Cheap fix: reformatted the string data. |
| 000105 18:22:59 | wa | om | code | test | 2 | grr... searching for the class name in a C++ external function definition mislabeled class name when preceded by &. Will modify style to make it work. |
Table 3-8. Defect Recording Log
| Student: | Victor B. Putz | Date: | 000106 |
| Program: | 3A |
| Defect found | Type | Reason | Phase Injected | Phase Removed | Fix time | Comments |
| 000106 09:25:01 | md | ig | design | compile | 1 | needed to add additional functionality as Eiffel blocks can start with many beginners |
| 000106 09:39:13 | sy | cm | code | compile | 0 | used C-style == instead of eiffel = for comparison |
| 000106 09:40:25 | sy | cm | code | compile | 0 | used c-style () after no-argument eiffel feature call |
| 000106 09:41:09 | sy | om | code | compile | 0 | used commas instead of semicolons to delimit argument lists |
| 000106 09:42:14 | sy | om | code | compile | 3 | forgot to add return type to feature declaration |
| 000106 09:46:15 | sy | ty | code | compile | 0 | mistakenly put a space between class and feature in feature call |
| 000106 09:47:31 | ic | ig | code | compile | 0 | forgot to add a "last_line_ends_with" feature in simple_loc_counter |
| 000106 09:49:27 | wn | ig | code | compile | 1 | used "item" instead of "at" to dereference keys in dictionary |
| 000106 09:51:02 | wn | cm | code | compile | 0 | mistakenly declared return type of class_name_from_last_line as BOOLEAN rather than STRING |
| 000106 09:52:23 | sy | cm | code | compile | 0 | forgot to add creation clause |
| 000106 09:54:34 | ma | cm | code | test | 1 | forgot to update indexes in loops |
| 000106 09:56:39 | wc | cm | code | test | 3 | Was updating the class name based on end of class rather than beginning of class. Silly. |
| 000106 10:01:21 | wc | cm | code | test | 0 | Was checking the "in_class implies nesting_level > 0" condition at wrong place |
| 000106 10:02:22 | wa | cm | code | test | 1 | was printing loc:feature_count instead of feature_count:loc |
| 000106 10:04:46 | wc | cm | code | test | 0 | was checking for "class" and not "class " at beginning of class declaration |
| 000106 10:10:32 | wa | ig | code | test | 2 | was not dealing properly with "deferred class" declarations |
| 000106 10:13:39 | wa | ig | code | test | 20 | adjusted feature count to include non-procedure features |
| 000106 10:34:04 | wa | ig | code | test | 8 | changed tab characters in last_line to spacses so left_adjust would work correctly |
| 000106 10:45:28 | wa | ig | code | test | 7 | added check to not count variables in local clause as features |