Program 3A: Class/Method LOC counter

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.

Given Requirements

 

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] NameNumber of Methods[Class] LOCTotal Program LOC
1AABC386 
 DEF28 
 GHI492 
    212
2A...   

Planning

Requirements

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.

Size estimate

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.

Resource estimate

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.

Development

Design

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.

Code

Holy smoke-- this took a great deal longer than I expected, mostly due to the vagarieties of the C++ language.

simple_input_parser.h

/*
*/

#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

/*
*/

simple_input_parser.cpp

/*
*/

#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 = "";
}

/*
*/

simple_loc_counter.h

/*
*/

#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

/*
*/

simple_loc_counter.cpp

/*
*/

#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";

/*
*/

class_metric.h

/*
*/


#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


/*
*/

class_metric.cpp

/*
*/

#include "class_metric.h"

class_metric::class_metric (void):
loc (0), feature_count (0)
{
}


/*
*/

class_based_loc_counter.h

/*
*/

#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


/*
*/

class_based_loc_counter.cpp

/*
*/

#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;
}

/*
*/

main.cpp

/*
*/

#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";
      }
    }
}


/*
*/

simple_input_parser.e

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

simple_loc_counter.cpp

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_metric.e

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_based_loc_counter.e

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

main.e

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

Compile

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.

Test

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] NameNumber of Methods[Class] LOCTotal Program LOC
1Anumber_list754 
    82
2Asimple_input_parser1252 
 simple_loc_counter26138 
    212
3Aclass_based_loc_counter24200 
 class_metric37 
 simple_input_parser1252 
 simple_loc_counter32177 
    463

Table 3-3. Test Results -- Program 3A, Eiffel

Program Number[Class] NameNumber of Methods[Class] LOCTotal Program LOC
1ANUMBER_LIST752 
 MAIN118 
    70
2ASIMPLE_INPUT_PARSER719 
 SIMPLE_LOC_COUNTER1552 
 MAIN111 
    82
3ACLASS_BASED_LOC_COUNTER2075 
 CLASS_METRIC512 
 SIMPLE_INPUT_PARSER719 
 SIMPLE_LOC_COUNTER2284 
 MAIN124 
    214

Postmortem

PSP0.1 Project Plan Summary

Table 3-4. Project Plan Summary

Student:Victor B. PutzDate:000106
Program:Class/Feature/LOC counterProgram#3A
Instructor:WellsLanguage:C++
Program SizePlanActualTo date
Base 212 
Deleted 1 
Modified 11 
Added 252 
Reused 00
Total New and Changed64263557
Total LOC 463730
Total new/reused   
Time in Phase (min):PlanActualTo DateTo Date%
Planning813376
Design15256911
Code328618529
Compile1012437
Test3614425439
Postmortem630508
Total107310638100
Defects Injected ActualTo DateTo Date %
Plan 000
Design 41523
Code 194670
Compile 023
Test 135
Total development 2467100
Defects Removed ActualTo DateTo Date %
Planning 000
Design 000
Code 41522
Compile 82639
Test 122639
Total development 2467100
After Development 00 
Eiffel code/compile/test
Time in Phase (min)ActualTo DateTo Date %
Code418740
Compile285626
Test617434
Total130217100
Defects InjectedActualTo DateTo Date %
Design1410
Code183890
Compile000
Test000
Total1942100
Defects RemovedActualTo DateTo Date %
Code012
Compile102457
Test91741
Total1942100

Time Recording Log

Table 3-5. Time Recording Log

Student:Victor B. PutzDate:000105
  Program:3A
StartStopInterruption TimeDelta timePhaseComments
000105 13:12:10000105 13:38:061213plan 
000105 13:39:55000105 14:05:45025design 
000105 14:13:06000105 15:39:21086code 
000105 15:50:37000105 16:03:28012compile 
000105 16:03:33000105 18:29:201144test 
000105 19:15:59000105 19:46:29030postmortem 
      

Table 3-6. Time Recording Log

Student:Victor B. PutzDate:000106
  Program:3A
StartStopInterruption TimeDelta timePhaseComments
000106 08:42:25000106 09:24:10041code 
000106 09:24:45000106 09:53:24028compile 
000106 09:53:52000106 10:55:43061test 
      

Defect Reporting Logs

Table 3-7. Defect Recording Log

Student:Victor B. PutzDate:000105
  Program:3A
Defect foundTypeReasonPhase InjectedPhase RemovedFix timeComments
000105 14:40:22mdomdesigncode9forgot to add a feature to extract the class name from the class description
000105 14:55:13mdomdesigncode7forgot to add a feature to extract the class name from an external feature
000105 15:21:33icigdesigncode3Added some refactorings to find scope operators, etc.
000105 15:29:26icigdesigncode2Added "has_entry_for_class_name" feature
000105 15:52:18chomcodecompile0Forgot to include required .h file for ENSURE macro
000105 15:55:19sycmcodecompile0accidentally included "static" in member implementation for data members
000105 15:56:20wncmcodecompile0Used "adjust_class_method_count" instead of "adjust_class_feature_count"
000105 15:57:25wncmcodecompile0Wrong name: m_class_name instead of m_current_class_name
000105 15:58:26wncmcodecompile0Wrong name: used "method" in name instead of "feature"
000105 15:59:18wncmcodecompile0Missed namespace requirement, ie std::string::size_type instead of just size_type
000105 16:00:29icomcodecompile0forgot to declare a feature as const
000105 16:01:56icomcodecompile1forgot to add class_map accessor function implementation
000105 16:05:06meexcodetest20Erroneous contract exception handling going on-- nothing to do with program 3a
000105 16:26:07mdigcodetest2Added more diagnostic stubs
000105 16:31:15miomcodetest0Forgot to reset the counter during construction. Silly, that.
000105 16:32:21wnigcodetest0Was looking for "class" at the beginning of class declarations instead of "class "
000105 16:34:14wncmcodetest1Used manifest constant "class" instead of class constant for class begin
000105 16:36:26wekncodetest13The logic to strip whitespace was screwy, ignoring one-character lines
000105 16:54:59wakncodetest4Due to improper timing of block comment nesting level calculation, end of block comments were "countable"
000105 17:00:01wakncodetest3Added check to make sure that the previous line had something useful on it before adding a line for external feature declaration
000105 17:04:34wakntesttest38Many problems handling namespaces in counting code. Unfortunately, a few errors were fixed without logging here.
000105 17:46:36wakncodetest8Error handling class declaration name extraction when inheritance used.
000105 18:00:05maomcodetest20Was treating the quoted "/*" marks as nested comments when they were string data. Cheap fix: reformatted the string data.
000105 18:22:59waomcodetest2grr... 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. PutzDate:000106
  Program:3A
Defect foundTypeReasonPhase InjectedPhase RemovedFix timeComments
000106 09:25:01mdigdesigncompile1needed to add additional functionality as Eiffel blocks can start with many beginners
000106 09:39:13sycmcodecompile0used C-style == instead of eiffel = for comparison
000106 09:40:25sycmcodecompile0used c-style () after no-argument eiffel feature call
000106 09:41:09syomcodecompile0used commas instead of semicolons to delimit argument lists
000106 09:42:14syomcodecompile3forgot to add return type to feature declaration
000106 09:46:15sytycodecompile0mistakenly put a space between class and feature in feature call
000106 09:47:31icigcodecompile0forgot to add a "last_line_ends_with" feature in simple_loc_counter
000106 09:49:27wnigcodecompile1used "item" instead of "at" to dereference keys in dictionary
000106 09:51:02wncmcodecompile0mistakenly declared return type of class_name_from_last_line as BOOLEAN rather than STRING
000106 09:52:23sycmcodecompile0forgot to add creation clause
000106 09:54:34macmcodetest1forgot to update indexes in loops
000106 09:56:39wccmcodetest3Was updating the class name based on end of class rather than beginning of class. Silly.
000106 10:01:21wccmcodetest0Was checking the "in_class implies nesting_level > 0" condition at wrong place
000106 10:02:22wacmcodetest1was printing loc:feature_count instead of feature_count:loc
000106 10:04:46wccmcodetest0was checking for "class" and not "class " at beginning of class declaration
000106 10:10:32waigcodetest2was not dealing properly with "deferred class" declarations
000106 10:13:39waigcodetest20adjusted feature count to include non-procedure features
000106 10:34:04waigcodetest8changed tab characters in last_line to spacses so left_adjust would work correctly
000106 10:45:28waigcodetest7added check to not count variables in local clause as features