/*************************************************************
   Copyright (c) 1993,1994 by Paul Long  All rights reserved.
**************************************************************/

/*************************************************************
   rules.c -   This source file contains a typical set of
               rules in the rules() function that define
               a simple metrics tool.
**************************************************************/


#include <limits.h>
#include "metre.h"

/*
   In addition to the parser's command-line options, e.g., -D, the rules look
   for a -Q option to suppress its verbose key and explanation that it
   otherwise generates at the beginning of the output.
*/
#define QUIET_OPT_CHAR        'Q'

/* Defines for warnings that are generated by the rules, not the parser. */
#define W_GOTO                0, "goto statement used (%u time%s)"
#define W_CONTINUE            1, "continue statement used (%u time%s)"
#define W_RETURNS             2, "Multiple return statements (%u)"
#define W_TAB_SPACE_INDENT    3, "Tab & space used on same line for indention"
#define W_MULT_STATEMENTS     4, "Multiple statements (%u) per line"
#define W_NONSTANDARD         5, "Non-standard character (0x%x) encountered"

/* Nicety to evaluate as "s" if argument is greater than one; otherwise, "". */
#define NUMBER(x)       ((x) > 1 ? "s" : "")

/* Assign max value to integer argument regardless of type, e.g., long int. */
#define TYPE_MAX(x) ((x = ~0) < 0 ? x = ~(1 << sizeof x * CHAR_BIT - 1) : x)

/*
   The rules maintain their function statistics in this structure.
   Could have also been just a bunch of separate variables, though.
*/
static struct {
   struct {
      unsigned min, max;
   } decisions;            /* Min and max binary decision points in function. */
   struct {
      unsigned min, max;   /* Min and max lines in a function. */
   } lines;
} per_func;

static BOOLEAN mixed_indent_lines;     /* Whether spaces _&_ tabs in indent. */
static unsigned mod_statements;        /* Total statements in module (file). */
static unsigned mod_func_lines;        /* Total function lines in module. */
static unsigned fcn_gotos;             /* How many gotos in a function. */
static unsigned fcn_continues;         /* How many continues in a function. */
static unsigned fcn_returns;           /* How many returns in a function. */
static unsigned fcn_depth_max;         /* Maximum nesting level in a function.*/

void rules(void)
{
   /*
      If at the beginning of a project and the quiet option has not been
      specified, print the key and explanation.
   */
   if (prj.begin && !option(QUIET_OPT_CHAR))
   {
      fprintf(out_fp, "Explanation and Guidelines:\n");
      fprintf(out_fp, "<          Minimum\n");
      fprintf(out_fp, ">          Maximum\n");
      fprintf(out_fp, "~          Average\n");
      fprintf(out_fp, "(nothing)  Total\n");
      fprintf(out_fp, "CCI        McCabe's Cyclomatic Complexity Index.  Number of binary\n");
      fprintf(out_fp, "           decision points plus one.  Should be less than 10.\n");
      fprintf(out_fp, "Depth      Maximum control structure depth.  Should be less than 6.\n\n");
      fprintf(out_fp, "A goto probably indicates that there should be another function.  Elses\n");
      fprintf(out_fp, "are probably a better solution than multiple return statements.  An else\n");
      fprintf(out_fp, "is probably a better solution than a continue statement.  A good size\n");
      fprintf(out_fp, "for a module is 30 lines or so with an upper limit of 60 lines.  One\n");
      fprintf(out_fp, "statement per line.  Don't mix tabs and spaces for indention.\n\n");
   }

   /* At the beginning of the module, initialize variables. */
   if (mod.begin)
   {
      per_func.decisions.max = 0;
      TYPE_MAX(per_func.decisions.min);
      per_func.lines.max = 0;
      TYPE_MAX(per_func.lines.min);
      mod_func_lines = 0;
      mod_statements = 0;
      mixed_indent_lines = FALSE;
   }

   /* At the end of the module, print a module summary. */
   if (mod.end)
   {
      fprintf(out_fp, "\n\nModule Summary\n");
      if (mod.functions != 0)
      {
         fprintf(out_fp, "   CCI:             <%u >%u ~%u\n",
               per_func.decisions.min + 1, per_func.decisions.max + 1,
               mod.decisions / mod.functions + 1);
         fprintf(out_fp, "   Lines:           <%u >%u ~%u %u\n",
               per_func.lines.min, per_func.lines.max,
               mod_func_lines / mod.functions, mod.lines.total);
      }
      fprintf(out_fp, "     Exec:          %u\n", mod.lines.exec);
      fprintf(out_fp, "     Comment:       %u\n", mod.lines.com);
      fprintf(out_fp, "     White:         %u\n", mod.lines.white);
      fprintf(out_fp, "   Statements:      %u\n", mod_statements);
      fprintf(out_fp, "   Functions:       %u\n", mod.functions);

      if (mixed_indent_lines)
         warn(W_TAB_SPACE_INDENT);
   }

   /* At the beginning of each function, initialize variables. */
   if (fcn.begin)
   {
      fcn_gotos = 0;
      fcn_continues = 0;
      fcn_returns = 0;
      fcn_depth_max = 0;
   }

   /*
      At the end of each function, set module-wide statistics variables
      and print a function summary.
   */
   if (fcn.end)
   {
      if (fcn.decisions > per_func.decisions.max)
            per_func.decisions.max = fcn.decisions;
      if (fcn.decisions < per_func.decisions.min)
            per_func.decisions.min = fcn.decisions;

      if (fcn.lines.total > per_func.lines.max)
            per_func.lines.max = fcn.lines.total;
      if (fcn.lines.total < per_func.lines.min)
            per_func.lines.min = fcn.lines.total;

      mod_func_lines += fcn.lines.total;
      mod_statements += fcn.low + fcn.high;

      fprintf(out_fp, "\n%s()\n", fcn_name());
      fprintf(out_fp, "   CCI:             %u\n", fcn.decisions + 1);
      fprintf(out_fp, "   Lines:           %u\n", fcn.lines.total);
      fprintf(out_fp, "     Exec:          %u\n", fcn.lines.exec);
      fprintf(out_fp, "     Comment:       %u\n", fcn.lines.com);
      fprintf(out_fp, "     White:         %u\n", fcn.lines.white);
      fprintf(out_fp, "   Statements:      %u\n", fcn.low + fcn.high);
      fprintf(out_fp, "   Depth:           >%u\n", fcn_depth_max);
      if (fcn_gotos > 0)
         warn(W_GOTO, fcn_gotos, NUMBER(fcn_gotos));
      if (fcn_continues > 0)
         warn(W_CONTINUE, fcn_continues, NUMBER(fcn_continues));
      if (fcn_returns > 1)
         warn(W_RETURNS, fcn_returns);
   }

   /* At the end of each statement, determine if greatest depth yet reached. */
   if (stm.end)
      if (stm.depth > fcn_depth_max)
         fcn_depth_max = stm.depth;

   /* Remember if line found with mixed indention (spaces and tabs). */
   if (lin.is_mixed_indent)
      mixed_indent_lines = TRUE;

   /* Print warning if a line contains more than one statement. */
   if (lin.statements > 0)
      warn(W_MULT_STATEMENTS, lin.statements + 1);

   /* Count gotos, continues, and returns. */
   if (keyword("goto"))
      ++fcn_gotos;

   if (keyword("continue"))
      ++fcn_continues;

   if (keyword("return"))
      ++fcn_returns;

   /*
      If character encountered that is not in Standard C's source
      character set, print warning.
   */
   if (lex.nonstandard)
      warn(W_NONSTANDARD, lex.nonstandard);
}
