/*****************************************************************************/
/*
                              C-c[o]d[e]c[ou]nt.c

Count of lines of code.
Count of lines of comment.


BUILD DETAILS
-------------
$ @BUILD_CCDCNT BUILD  !compile+link
$ @BUILD_CCDCNT LINK   !link-only


COPYRIGHT
---------
Copyright (C) 2015-2020 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY
---------------
21-JAN-2015  MGD  v1.0.0, initial (q&d) development
*/
/*****************************************************************************/

#define SOFTWAREVN "1.0.0"
#define SOFTWARENM "CCDCNT"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unixio.h>

/* VMS header files */
#include <fabdef.h>
#include <namdef.h>
#include <rmsdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* macros */
#define FI_LI __FILE__, __LINE__

#define ODS_MAX_FILE_NAME_LENGTH 1024
#define ODS2_MAX_FILE_NAME_LENGTH 255

#define VMSnok(x) (!((x) & STS$M_SUCCESS))
#define VMSok(x) ((x) & STS$M_SUCCESS)

#define USHORTPTR __unaligned unsigned short*
#define SAME2(ptr,val) (*(USHORTPTR)(ptr) == val)
#define SLASHESC(ptr) (*ptr == '\\' && *(ptr+1))

int Debug = 0;

char  Utility [] = "CCDCNT";

/*****************************************************************************/
/*
*/

main (int argc, char** argv)
       
{
static char Dash80 [] = "-----------------------------------------\
-------------------------------------";

   int  cnt, size, status,
        BothCharCount,
        BothLineCount,
        CodeCharCount,
        CodeLineCount,
        CommentCharCount,
        CommentLineCount,
        CommentCount,
        DoOverAll = 0,
        ExpFileNameLength,
        FileCharCount,
        FileLineCount,
        PrevCodeCharCount,
        PrevCommentCharCount,
        ResFileNameLength,
        TotalBothCharCount = 0,
        TotalBothLineCount = 0,
        TotalCharCount = 0,
        TotalCodeCharCount = 0,
        TotalCodeLineCount = 0,
        TotalCommentCharCount = 0,
        TotalCommentLineCount = 0,
        TotalCommentCount = 0,
        TotalFileCount = 0,
        TotalLineCount = 0;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1],
         ResFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   char  *aptr, *cptr, *fsptr, *sptr, *zptr;
   stat_t  statBuf;
   FILE  *fptr;
   struct FAB  SearchFab;
   struct NAM  SearchNam;
#ifdef ODS_EXTENDED
   struct NAML  SearchNaml;
#endif /* ODS_EXTENDED */

   /*********/
   /* begin */
   /*********/

   fsptr = "*.C;0";
   for (cnt = 1; cnt < argc; cnt++)
   {
      if (argv[cnt][0] == '/')
      {
         if (!strcasecmp(argv[cnt],"/overall") ||
             !strcasecmp(argv[cnt],"/brief"))
            DoOverAll = 1;
      }
      else
         fsptr = argv[cnt];
   }

   SearchFab = cc$rms_fab;
   SearchFab.fab$l_dna = "*.*;0";
   SearchFab.fab$b_dns = 5;

#ifdef ODS_EXTENDED
   SearchFab.fab$l_fna = (char*)-1;
   SearchFab.fab$b_fns = 0;
   SearchFab.fab$l_nam = (struct namdef*)&SearchNaml;

   ENAMEL_RMS_NAML(SearchNaml)
   SearchNaml.naml$l_long_filename = fsptr;
   SearchNaml.naml$l_long_filename_size = strlen(fsptr);
   SearchNaml.naml$l_long_expand = ExpFileName;
   SearchNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
   SearchNaml.naml$l_long_result = ResFileName;
   SearchNaml.naml$l_long_result_alloc = sizeof(ResFileName)-1;
#else /* ODS_EXTENDED */
   SearchFab.fab$l_fna = fsptr;
   SearchFab.fab$b_fns = strlen(fsptr);
   SearchFab.fab$l_nam = &SearchNam;

   SearchNam = cc$rms_nam;
   SearchNam.nam$l_esa = ExpFileName;
   SearchNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   SearchNam.nam$l_rsa = ResFileName;
   SearchNam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
#endif /* ODS_EXTENDED */

   if (VMSnok (status = sys$parse (&SearchFab, 0, 0)))
      return (status);

#ifdef ODS_EXTENDED
   SearchNaml.naml$l_long_ver[SearchNaml.naml$l_long_ver_size] = '\0';
   ExpFileNameLength = (SearchNaml.naml$l_long_ver - ExpFileName) +
                       SearchNaml.naml$l_long_ver_size;
#else /* ODS_EXTENDED */
   SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0';
   ExpFileNameLength = (SearchNam.nam$l_ver - ExpFileName) +
                       SearchNam.nam$b_ver;
#endif /* ODS_EXTENDED */
   if (Debug)
      fprintf (stdout, "ExpFileName %d |%s|\n",
               ExpFileNameLength, ExpFileName);

   /*****************/
   /* for each file */
   /*****************/

   while (VMSok (status = sys$search (&SearchFab, 0, 0)))
   {
#ifdef ODS_EXTENDED
      SearchNaml.naml$l_long_ver[SearchNaml.naml$l_long_ver_size] = '\0';
      ResFileNameLength = (SearchNaml.naml$l_long_ver - ResFileName) +
                          SearchNaml.naml$l_long_ver_size;
#else /* ODS_EXTENDED */
      SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0';
      ResFileNameLength = (SearchNam.nam$l_ver - ResFileName) +
                          SearchNam.nam$b_ver;
#endif /* ODS_EXTENDED */
      if (Debug)
         fprintf (stdout, "ResFileName %d |%s|\n",
                 ResFileNameLength, ResFileName);

      if (stat (ResFileName, &statBuf) < 0)
      {
         fprintf (stdout, "%s-E-ACCESS, %%X%08.08X %s\n",
                  Utility, vaxc$errno, ResFileName);
         continue;
      }

      size = statBuf.st_size;
      if (Debug) fprintf (stdout, "%d bytes\n", size);
      if (!(aptr = calloc (1, size+1024))) exit (vaxc$errno);

      /****************/
      /* process file */
      /****************/

      if ((fptr = fopen (ResFileName, "r", "shr=get")) != NULL)
      {
         if (statBuf.st_fab_rfm == FAB$C_VAR ||
             statBuf.st_fab_rfm == FAB$C_VFC)
         {
            zptr = (sptr = aptr) + size;
            while (fgets (sptr, zptr-sptr, fptr))
               sptr += strlen(sptr);
            size = sptr - aptr;
         }
         else
            size = fread (aptr, 1, size, fptr);
         if (Debug) fprintf (stdout, "%d bytes\n", size);

         fclose (fptr);

         if (!TotalFileCount)
            fprintf (stdout, "\
     ------Total------ -----------Code------------ ----------Comment----------\n\
       Lines     Chars   Lines          Chars        Lines          Chars\n\
     ------- --------- ------- ---- --------- ---- ------- ---- --------- ----\n\
");

         if (size)
         {
            BothLineCount = BothCharCount = CodeCharCount = CodeLineCount =
               CommentCharCount = CommentLineCount = CommentCount =
               FileCharCount = FileLineCount = PrevCodeCharCount =
               PrevCommentCharCount = 0;

            TotalFileCount++;
            FileCharCount = size;
            zptr = (cptr = aptr) + size;
            while (cptr < zptr)
            {
               if (SAME2(cptr,'/*'))
               {
                  /***********/
                  /* comment */
                  /***********/

                  cptr += 2;
                  CommentCharCount += 2;
                  CommentCount++;
                  while (cptr < zptr && !SAME2(cptr,'*/'))
                  {
                     if (SLASHESC(cptr))
                     {
                        CommentCharCount++;
                        cptr++;
                     }
                     if (*cptr == '\n')
                     {
                        FileLineCount++;
                        BothLineCount++;
                     }
                     if (!isspace(*cptr)) CommentCharCount++;
                     cptr++;
                  }
                  if (cptr < zptr)
                  {
                     CommentCharCount += 2;
                     cptr += 2;
                  }
               }
               else
               if (SAME2(cptr,'//'))
               {
                  /********************/
                  /* comment (to EOL) */
                  /********************/

                  cptr += 2;
                  CommentCharCount += 2;
                  CommentCount++;
                  // to end of line
                  while (cptr < zptr && *cptr != '\n')
                  {
                     if (SLASHESC(cptr))
                     {
                        CommentCharCount++;
                        cptr++;
                     }
                     if (!isspace(*cptr)) CommentCharCount++;
                     cptr++;
                  }
                  if (cptr < zptr) cptr++;
               }
               else
               if (*cptr == '\"')
               {
                  /***********/
                  /* literal */
                  /***********/

                  CodeCharCount++;
                  cptr++;
                  while (cptr < zptr && *cptr != '\"')
                  {
                     if (SLASHESC(cptr))
                     {
                        CodeCharCount++;
                        cptr++;
                     }
                     else
                     if (*cptr == '\n')
                     {
                        FileLineCount++;
                        BothLineCount++;
                        CodeLineCount++;
                     }
                     CodeCharCount++;
                     cptr++;
                  }
                  if (cptr < zptr)
                  {
                     CodeCharCount++;
                     cptr++;
                  }
               }
               else
               if (*cptr == '\n')
               {
                  /************/
                  /* new line */
                  /************/

                  FileLineCount++;
                  if (CodeCharCount > PrevCodeCharCount || 
                      CommentCharCount > PrevCommentCharCount)
                  {
                     BothLineCount++;
                     if (CodeCharCount > PrevCodeCharCount)
                        CodeLineCount++;
                     PrevCodeCharCount = CodeCharCount;
                     if (CommentCharCount > PrevCommentCharCount)
                        CommentLineCount++;
                     PrevCommentCharCount = CommentCharCount;
                  }
                  cptr++;
               }
               else
               if (!isspace(*cptr))
               {
                  /*************************/
                  /* significant non-space */
                  /*************************/

                  CodeCharCount++;
                  cptr++;
               }
               else
                  cptr++;
            }

            BothCharCount = CodeCharCount + CommentCharCount;
            TotalBothCharCount += BothCharCount;
            TotalBothLineCount += BothLineCount;
            TotalCharCount += FileCharCount;
            TotalCodeCharCount += CodeCharCount;
            TotalCodeLineCount += CodeLineCount;
            TotalCommentCharCount += CommentCharCount;
            TotalCommentLineCount += CommentLineCount;
            TotalCommentCount += CommentCount;
            TotalLineCount += FileLineCount;

            if (!DoOverAll)
            {
               if (strchr(fsptr,'*'))
                  fprintf (stdout, "%s%*.*s\n",
                           ResFileName,
                           78-ResFileNameLength, 78-ResFileNameLength, Dash80);

               fprintf (stdout,
"%04d %7d %9d %7d %3d%% %9d %3d%% %7d %3d%% %9d %3d%%\n",
                        TotalFileCount,
                        FileLineCount,
                        TotalCharCount,
                        CodeLineCount, CodeLineCount * 100 / BothLineCount,
                        CodeCharCount, CodeCharCount * 100 / BothCharCount,
                        CommentLineCount, CommentLineCount * 100 / BothLineCount,
                        CommentCharCount, CommentCharCount * 100 / BothCharCount);
            }
         }
         else
            fprintf (stdout, "%s-E-READ, %%X%08.08X %s\n",
                     Utility, vaxc$errno, ResFileName);
      }
      else
         fprintf (stdout, "%s-E-OPEN, %%X%08.08X %s\n",
                  Utility, vaxc$errno, ResFileName);

      free (aptr);
   }

   /**********/
   /* report */
   /**********/

   if (!TotalFileCount) exit (status);

   if (TotalFileCount > 1)
   {
      if (!DoOverAll)
         fprintf (stdout, "%33.33s OVERALL %36.36s\n", Dash80, Dash80);

      fprintf (stdout,
"%04d %7d %9d %7d %3d%% %9d %3d%% %7d %3d%% %9d %3d%%\n",
               TotalFileCount,
               TotalLineCount,
               TotalCharCount,
               TotalCodeLineCount,
               TotalCodeLineCount * 100 / TotalBothLineCount,
               TotalCodeCharCount,
               TotalCodeCharCount * 100 / TotalBothCharCount,
               TotalCommentLineCount,
               TotalCommentLineCount * 100 / TotalBothLineCount,
               TotalCommentCharCount,
               TotalCommentCharCount * 100 / TotalBothCharCount);
   }

   if (status == RMS$_NMF) status = SS$_NORMAL;

   return (status);
}

/*****************************************************************************/

