/******************************************************************/
/* Program -MVMASM- Assembly language parser for MVS assembly     */
/* language.  To be used as a parser in TeamConnection.           */
/* Written by KM - 10/10/97                                       */
/******************************************************************/
/* 5696-737 (C) COPYRIGHT IBM CORP. 1988-97                       */
/* LICENSED MATERIAL - PROGRAM PROPERTY OF IBM                    */
/******************************************************************/


#if defined(__NT__)
#include <io.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mvsasmp.h"


/*****************************************/
/* Global variables                      */
/*****************************************/
int  NumSurpluswords = 0, NumIncludes = 0;
char Infilename[80], Outfilename[80],
     *IncludeList[MAX_INCLUDES];
FILE *Infile, *Outfile;
/*****************************************/


/* Retrieves parameter */
void getparam(int argc, char **argv, char *infilename, char *outfilename)
{
   /* Get parameter */
   if (argc-1 == 0)
   {
      printf("\nInvalid number of parameters\n");
      printf("Syntax: MVSASMP  <input file>  [output file]\n\n");
      exit(1);
   }
   else
   {
      strcpy(infilename, argv[1]);
      if (argc-1 >= 2)
         strcpy(outfilename, argv[2]);
      else
         strcpy(outfilename, OUTFILE);
   }
}


/* Opens input and output files */
FILE *openfile(char *filename, char *mode)
{
   FILE *file;

   if (!(file = fopen(filename, mode)))
   {
      printf("\nError opening %s file\n", mode[0]=='r' ? "ASM source input" : "output");
      exit(1);
   }

   return file;
}


/* Loads the MVS surplus keywords from file if file exists */
void loadsurpluswords()
{
   char line[MAX_LINELENGTH], word[MAX_LINELENGTH];
   FILE *file;

   if ((file = fopen(SURPLUSFILE, "r")))
   {
      while (fgets(line, sizeof(line), file))
      {
         /* Remove linefeed character at end of string */
         if (line[0] && line[strlen(line)-1] == '\n')
            line[strlen(line)-1] = 0;

         /* Add word to list */
         if (sscanf(line, "%s", word) == 1)
            if (NumSurpluswords < MAX_SURPLUSWORDS)
            {
               SurplusWords[NumSurpluswords] = malloc(strlen(word)+1);
               strcpy(SurplusWords[NumSurpluswords++], word);
            }
            else
            {
               printf("\nError: Maximum keywords of %d in %s has been exceeded\n", MAX_SURPLUSWORDS, SURPLUSFILE);
               exit(1);
            }
      }

      fclose(file);
   }
}


/* Performs binary search on keyword */
int isMVSWord(int top, int bottom, char **dbase, char *keyword)
{
   int middle;

   /* Check for terminating condition */
   if (top+1 == bottom)
      if (strcmp(keyword, dbase[top]) != 0 &&
          strcmp(keyword, dbase[bottom]) != 0)
         return 0;
      else
         return 1;

   /* Use binary sort to find keyword */
   middle = (top + bottom) / 2;
   if (strcmp(keyword, dbase[middle]) == 0)
      return 1;
   if (strcmp(keyword, dbase[middle]) < 0)
      return isMVSWord(top, middle, dbase, keyword);
   else
      return isMVSWord(middle, bottom, dbase, keyword);
}


/* Determines if the keyword is an MVS */
/* keyword using a binary sort algorithm. */
/* Returns 1 if true and 0 otherwise */
int isMVSKeyword(char *keyword)
{
   int pos;

   /* Check standard keywords, then check surplsu file */
   if (!isMVSWord(0, NUM_KEYWORDS-1, Keywords, keyword))
   {
      for (pos = 0; pos < NumSurpluswords; pos++)
         if (strcmp(keyword, SurplusWords[pos]) == 0)
            return 1;
   }
   else
      return 1;

   return 0;
}


/* Calculates a hash value based on a string */
int hashfunc(char *string, int maxentries)
{
   long pos, hashvalue;

   /* Calculate hash value */
   for (pos = hashvalue = 0; pos < strlen(string); pos++)
      hashvalue += string[pos];
   
   /* Create a wide distribution */
   hashvalue *= string[0];

   /* Map large value to array boundaries */
   hashvalue %= maxentries;

   return (int) hashvalue;
}


/* Determines if an include file is a duplicate */
/* Adds the include file to a list if it's not a duplicate */
/* Returns 1 if true and 0 if otherwise */
int isDuplicate(char *includeName)
{
   static int funcInit = 0;

   int pos, hashpos;

   /* Initialize */
   if (!funcInit)
   {
      memset(IncludeList, 0, sizeof(IncludeList));
      funcInit = 1;
   }

   /* Assume duplicate does not exist */
   /* if memory bounds have been exceeded */
   if (NumIncludes == MAX_INCLUDES)
      return 0;

   /* Calculate hash position */
   hashpos = hashfunc(includeName, MAX_INCLUDES);

   /* Search for entry in list */
   for (pos = hashpos;
        IncludeList[pos % MAX_INCLUDES] && pos-hashpos < MAX_INCLUDES; pos++)
      if (strcmp(IncludeList[pos % MAX_INCLUDES], includeName) == 0)
         return 1;

   /* Find an empty position in list */
   while (IncludeList[hashpos])
      hashpos++;

   /* Add new include file name */
   IncludeList[hashpos] = malloc(strlen(includeName)+1);
   strcpy(IncludeList[hashpos], includeName);

   /* Update number of entries */
   NumIncludes++;

   return 0;
}


/**************************************************************/
/* Parses the ASM file for COPY statements:                   */
/**************************************************************/
/*  If first col = '*', the line is a comment.                */
/*  All COPY statements will begin on column 10.              */
/*  If COPY statement is ommited, parser will search database */
/*  of MVS keywords for word.  If no match is found, the      */
/*  word found in place of the COPY statement will be added   */
/*  to the list of include files for the build.               */
/**************************************************************/
void parsefile(FILE *infile, FILE *outfile)
{
   int  pos, foundIncludeFile;
   char line[MAX_LINELENGTH], labelname[MAX_LINELENGTH],
        commandname[MAX_LINELENGTH], copyname[MAX_LINELENGTH],
        infilenameUpper[80];

   /*********************************************/
   /* Convert input file name into a valid form */
   /*********************************************/
   for (pos = 0; pos <= strlen(Infilename); pos++)
      if (Infilename[pos] >= 'a' && Infilename[pos] <= 'z')
         infilenameUpper[pos] = Infilename[pos] - ('a'-'A');
      else
         infilenameUpper[pos] = Infilename[pos];

   /* Remove path from file name */
   for (pos = strlen(infilenameUpper) - 1; 
        pos >= 0 && infilenameUpper[pos] != '\\' && infilenameUpper[pos] != '/'; 
        pos--);
   if (pos >= 0 && infilenameUpper[pos] == '\\' || infilenameUpper[pos] == '/')
      strcpy(infilenameUpper, &infilenameUpper[pos]+1);
   
   /* Remove extension from file name */
   if (strchr(infilenameUpper, '.'))
      *(strchr(infilenameUpper, '.')) = 0;

   /********************/
   /* Parse input file */
   /********************/
   while (fgets(line, sizeof(line), infile))
   {
      /* Assume we will not find a file */
      foundIncludeFile = 0;

      /* Remove linefeed character at end of string */
      if (line[0] && line[strlen(line)-1] == '\n')
         line[strlen(line)-1] = 0;

      /* Append spaces to the line */
      while (strlen(line) < MAX_LINELENGTH)
         strcat(line, " ");

      /* Get label name */
      sscanf(line, "%s", labelname);
      if (isspace(line[0]))
         strcpy(labelname, "*****");

      /* Skip comments */
      if (!strchr(SKIP_SYMBOLS, line[0]) && 
          !strstr(SKIP_LABELS, labelname) &&
          isspace(line[COPYCOLUMN-1]))
      {
         /*************************************************************/
         /* Check for 'COPY' statement and extract next parameter.    */
         /* If not found, determine if keyword refers to an           */
         /* include file by searching for it in MVS keyword database. */
         /* If keyword is NOT an MVS keyword, then it refers          */
         /* to an include file.                                       */
         /*************************************************************/
         if (sscanf(&line[COPYCOLUMN], "%s %s", commandname, copyname) == 2 &&
             strcmp(commandname, KEYWORD_COPY) == 0)
            foundIncludeFile = 1;
         else
            if (sscanf(&line[COPYCOLUMN], "%s", copyname) == 1 &&
                !isMVSKeyword(copyname))
               foundIncludeFile = 1;
      }

      /*************************************/
      /* Verify include file name is valid */
      /*************************************/
      if (foundIncludeFile)
         if ((copyname[0] >= '0' && copyname[0] <= '9') ||
             ((copyname[0] < 'A' || copyname[0] > 'Z') && !strchr("$#@", copyname[0])) ||
             strlen(copyname) > MAX_ASM_FILENAMELENGTH ||
             strcmp(copyname, infilenameUpper) == 0 ||
             isDuplicate(copyname))
            foundIncludeFile = 0;

      /**********************************/
      /* Write valid include file names */
      /**********************************/
      if (foundIncludeFile)
         fprintf(outfile, "%s\n", copyname);
   }
}



/**************************************/
/* Main Program                       */
/**************************************/
int main(int argc, char **argv)
{
   /* Get parameter */
   getparam(argc, argv, Infilename, Outfilename);

   /* Open files */
   if ((Infile = openfile(Infilename, "r")))
      Outfile = openfile(Outfilename, "w");

   /* Read MVS surplus keyword database */
   loadsurpluswords();

   /* Parse input file */
   parsefile(Infile, Outfile);

   /* Close files */
   fclose(Infile);
   fclose(Outfile);

   return 0;
}
