/*****************************************************************************/
/*
                              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-2022 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY
---------------
22-FEB-2022  MGD  v1.1.0, ignore directories
21-JAN-2015  MGD  v1.0.0, initial (q&d) development
*/
/*****************************************************************************/

#define SOFTWAREVN "1.1.0"
#define SOFTWARENM "CCDCNT"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  error VAX no longer implemented
#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>
#include <starlet.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;
      }

      if (S_ISDIR(statBuf.st_mode)) 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);
         if (TotalFileCount)
            fprintf (stdout, "\
     ------Total------ -----------Code------------ ----------Comment----------\n\
       Lines     Chars   Lines          Chars        Lines          Chars\n\
     ------- --------- ------- ---- --------- ---- ------- ---- --------- ----\n\
");
      }

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

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

