sourcepawn/compiler/scpack.c
author David Anderson <danderson@mozilla.com>
Thu Aug 08 09:40:23 2013 -0700 (2013-08-08)
changeset 3976 bc77cfde7c5c
parent 133 d32bd440f09f
permissions -rw-r--r--
Add -Wno-narrowing to support building on GCC-4.7 (no bug, r=me).
     1 /* compress.c -- Byte Pair Encoding compression */
     2 /* Copyright 1996 Philip Gage */
     3 
     4 /* This program appeared in the September 1997 issue of
     5  * C/C++ Users Journal. The original source code may still
     6  * be found at the web site of the magazine (www.cuj.com).
     7  *
     8  * It has been modified by me (Thiadmer Riemersma) to
     9  * compress only a section of the input file and to store
    10  * the compressed output along with the input as "C" strings.
    11  *
    12  * Compiling instructions:
    13  *  Borland C++ 16-bit (large memory model is required):
    14  *      bcc -ml scpack.c
    15  *
    16  *  Watcom C/C++ 32-bit:
    17  *      wcl386 scpack.c
    18  *
    19  *  GNU C (Linux), 32-bit:
    20  *      gcc scpack.c -o scpack
    21  */
    22 
    23 #include <assert.h>
    24 #include <limits.h>
    25 #include <stdio.h>
    26 #include <stdlib.h>
    27 #include <string.h>
    28 
    29 #if UINT_MAX > 0xFFFFU
    30   #define MAXSIZE 1024*1024L
    31 #else
    32   #define MAXSIZE UINT_MAX      /* Input file buffer size */
    33 #endif
    34 #define HASHSIZE 8192   /* Hash table size, power of 2 */
    35 #define THRESHOLD   3   /* Increase for speed, min 3 */
    36 
    37 #define START_TOKEN "#ifdef SCPACK" /* start reading the buffer here */
    38 #define NAME_TOKEN  "#define SCPACK_TABLE"
    39 #define SEP_TOKEN   "#define SCPACK_SEPARATOR"
    40 #define TERM_TOKEN  "#define SCPACK_TERMINATOR"
    41 #define TEMPFILE    "~SCPACK.TMP"
    42 static char tablename[32+1] = "scpack_table";
    43 static char separator[16]=",";
    44 static char terminator[16]="";
    45 
    46 int compress(unsigned char *buffer, unsigned buffersize, unsigned char pairtable[128][2])
    47 {
    48   unsigned char *left, *right, *count;
    49   unsigned char a, b, bestcount;
    50   unsigned i, j, index, bestindex, code=128;
    51 
    52   /* Dynamically allocate buffers and check for errors */
    53   left = (unsigned char *)malloc(HASHSIZE);
    54   right = (unsigned char *)malloc(HASHSIZE);
    55   count = (unsigned char *)malloc(HASHSIZE);
    56   if (left==NULL || right==NULL || count==NULL) {
    57     printf("Error allocating memory\n");
    58     exit(1);
    59   }
    60 
    61   /* Check for errors */
    62   for (i=0; i<buffersize; i++)
    63     if (buffer[i] > 127) {
    64       printf("This program works only on text files (7-bit ASCII)\n");
    65       exit(1);
    66     }
    67 
    68   memset(pairtable, 0, 128*2*sizeof(char));
    69 
    70   do {  /* Replace frequent pairs with bytes 128..255 */
    71 
    72     /* Enter counts of all byte pairs into hash table */
    73     memset(count,0,HASHSIZE);
    74     for (i=0; i<buffersize-1; i++) {
    75       a = buffer[i];
    76       b = buffer[i+1];
    77       /* ignore any pair with a '\0' */
    78       if (a == 0 || b == 0)
    79         continue;
    80       index = (a ^ (b << 6)) & (HASHSIZE-1);
    81       while ((left[index] != a || right[index] != b) &&
    82              count[index] != 0)
    83         index = (index + 1) & (HASHSIZE-1);
    84       left[index] = a;
    85       right[index] = b;
    86       if (count[index] < 255)
    87         count[index] += (unsigned char)1;
    88     }
    89 
    90     /* Search hash table for most frequent pair */
    91     bestcount = THRESHOLD - 1;
    92     for (i=0; i<HASHSIZE; i++) {
    93       if (count[i] > bestcount) {
    94         bestcount = count[i];
    95         bestindex = i;
    96       }
    97     }
    98 
    99     /* Compress if enough occurrences of pair */
   100     if (bestcount >= THRESHOLD) {
   101 
   102       /* Add pair to table using code as index */
   103       a = pairtable[code-128][0] = left[bestindex];
   104       b = pairtable[code-128][1] = right[bestindex];
   105 
   106       /* Replace all pair occurrences with unused byte */
   107       for (i=0, j=0; i<buffersize; i++, j++)
   108         if (a == buffer[i] && b == buffer[i+1]) {
   109           buffer[j] = (unsigned char)code;
   110           ++i;
   111         }
   112         else
   113           buffer[j] = buffer[i];
   114       buffersize = j;
   115     }
   116     else
   117       break;
   118   } while (++code < 255);
   119 
   120   /* done */
   121   free(left); free(right); free(count);
   122   return buffersize;  /* return adjusted buffersize */
   123 }
   124 
   125 static int strmatch(char *str, char *token, int *indent)
   126 {
   127   int i = 0;
   128 
   129   /* skip whitespace */
   130   while (*str==' ' || *str=='\t') {
   131     str++;
   132     i++;
   133   } /* while */
   134   if (strncmp(str,token,strlen(token))!=0)
   135     return 0;
   136   if (indent != NULL)
   137     *indent = i;
   138   return 1;
   139 }
   140 
   141 static void check_if(char *str,int linenr)
   142 {
   143   if (strmatch(str,"#if",NULL)) {
   144     printf("Error: \"#if...\" preprocessor statement should not be in SCPACK section "
   145            "(line %d)\n", linenr);
   146     exit(1);
   147   } /* if */
   148 }
   149 
   150 static int check_tablename(char *str)
   151 {
   152   int i;
   153 
   154   if (strmatch(str,NAME_TOKEN,NULL)) {
   155     str += strlen(NAME_TOKEN);
   156     while (*str==' ' || *str=='\t')
   157       str++;
   158     for (i=0; i<(sizeof tablename - 1) && *str!='\0' && strchr(" \t\n",*str)==NULL; i++, str++)
   159       tablename[i] = *str;
   160     tablename[i] = '\0';
   161     return 1;
   162   } /* if */
   163   return 0;
   164 }
   165 
   166 static int check_separator(char *str)
   167 {
   168   int i;
   169 
   170   if (strmatch(str,SEP_TOKEN,NULL)) {
   171     str += strlen(SEP_TOKEN);
   172     while (*str==' ' || *str=='\t')
   173       str++;
   174     for (i=0; i<(sizeof separator - 1) && *str!='\0' && strchr(" \t\n",*str)==NULL; i++, str++)
   175       separator[i] = *str;
   176     separator[i] = '\0';
   177     return 1;
   178   } /* if */
   179 
   180   if (strmatch(str,TERM_TOKEN,NULL)) {
   181     str += strlen(TERM_TOKEN);
   182     while (*str==' ' || *str=='\t')
   183       str++;
   184     for (i=0; i<(sizeof terminator - 1) && *str!='\0' && strchr(" \t\n",*str)==NULL; i++, str++)
   185       terminator[i] = *str;
   186     terminator[i] = '\0';
   187     return 1;
   188   } /* if */
   189 
   190   return 0;
   191 }
   192 
   193 /* readbuffer
   194  * Reads in the input file and stores all strings in the
   195  * section between "#ifdef SCPACK" and "#else" in a buffer.
   196  * Only text that is between double quotes is added to the
   197  * buffer; the \" escape code is handled. Multiple strings
   198  * on one line are handled.
   199  */
   200 unsigned readbuffer(FILE *input, unsigned char *buffer)
   201 {
   202   char str[256];
   203   unsigned buffersize;
   204   int i,linenr;
   205 
   206   linenr=0;
   207   buffersize=0;
   208 
   209   rewind(input);
   210   while (!feof(input)) {
   211     while (fgets(str,sizeof str,input)!=NULL) {
   212       linenr++;
   213       check_tablename(str);
   214       check_separator(str);
   215       if (strmatch(str,START_TOKEN,NULL))
   216         break;
   217     } /* while */
   218     if (!strmatch(str,START_TOKEN,NULL))
   219       return buffersize;  /* no (more) section found, quit */
   220 
   221     while (fgets(str,sizeof str,input)!=NULL) {
   222       linenr++;
   223       check_if(str,linenr);
   224       if (check_tablename(str))
   225         printf("Error: table name definition should not be in SCPACK section (line %d)\n", linenr);
   226       check_separator(str);
   227       if (strmatch(str,"#else",NULL))
   228         break;          /* done */
   229       /* add to the buffer only what is between double quotes */
   230       i=0;
   231       do {
   232         while (str[i]!='\0' && str[i]!='"')
   233           i++;
   234         if (str[i]=='"') {
   235           /* we are in a string */
   236           i++;
   237           while (str[i]!='\0' && str[i]!='"') {
   238             /* handle escape sequences */
   239             if (str[i]=='\\') {
   240               i++;
   241               switch (str[i]) {
   242               case 'a': /* alarm */
   243                 buffer[buffersize++]='\a';
   244                 i++;
   245                 break;
   246               case 'b': /* backspace */
   247                 buffer[buffersize++]='\b';
   248                 i++;
   249                 break;
   250               case 'f': /* form feed */
   251                 buffer[buffersize++]='\f';
   252                 i++;
   253                 break;
   254               case 'n': /* newline */
   255                 buffer[buffersize++]='\n';
   256                 i++;
   257                 break;
   258               case 'r': /* carriage return */
   259                 buffer[buffersize++]='\n';
   260                 i++;
   261                 break;
   262               case 't': /* tab */
   263                 buffer[buffersize++]='\t';
   264                 i++;
   265                 break;
   266               case '\'':
   267                 buffer[buffersize++]='\'';
   268                 i++;
   269                 break;
   270               case '"':
   271                 buffer[buffersize++]='"';
   272                 i++;
   273                 break;
   274               default:
   275                 // ??? octal character code escapes and hexadecimal escapes
   276                 //     not supported
   277                 printf("Unknown escape sequence '\\%c' on line %d\n",
   278                        str[i], linenr);
   279               } /* switch */
   280             } else {
   281               buffer[buffersize++]=str[i++];
   282             } /* if */
   283           } /* while */
   284           if (str[i]=='"') {
   285             buffer[buffersize++]='\0'; /* terminate each string */
   286             i++;
   287           } else {
   288             printf("Error: unterminated string on line %d\n",linenr);
   289           } /* if */
   290         } /* if */
   291       } while (str[i]!='\0');
   292     } /* while - in SCPACK section */
   293     /* put in another '\0' to terminate the section */
   294     buffer[buffersize++]='\0';
   295   } /* while - !feof(input) */
   296   return buffersize;
   297 }
   298 
   299 static void write_pairtable(FILE *output, unsigned char pairtable[128][2], char *tablename)
   300 {
   301   int i;
   302 
   303   /* dump the pair table */
   304   fprintf(output, "/*-*SCPACK start of pair table, do not change or remove this line */\n");
   305   fprintf(output, "unsigned char %s[][2] = {", tablename);
   306   for (i=0; i<128 && pairtable[i][0]!=0 && pairtable[i][1]!=0; i++) {
   307     if ((i % 16)==0)
   308       fprintf(output, "\n  ");
   309     else
   310       fprintf(output, " ");
   311     fprintf(output, "{%d,%d}", pairtable[i][0], pairtable[i][1]);
   312     /* check if something follows this pair */
   313     if (i+1<128 && pairtable[i+1][0]!=0 && pairtable[i+1][1]!=0)
   314       fprintf(output, ",");
   315   } /* for */
   316   fprintf(output, "\n};\n");
   317   fprintf(output, "/*-*SCPACK end of pair table, do not change or remove this line */\n");
   318 }
   319 
   320 void writefile(FILE *input, FILE *output, unsigned char *buffer, unsigned buffersize, unsigned char pairtable[128][2])
   321 {
   322   char str[256];
   323   int insection, indent, needseparator;
   324   unsigned char *bufptr;
   325 
   326   bufptr = buffer;
   327   insection = 0;
   328 
   329   rewind(input);
   330   while (!feof(input)) {
   331     while (fgets(str,sizeof str,input)!=NULL) {
   332       fprintf(output,"%s",str);
   333       if (check_tablename(str)) {
   334         write_pairtable(output, pairtable, tablename);
   335         /* strip an existing pair table from the file */
   336         if (fgets(str,sizeof str,input)!=NULL) {
   337           if (strmatch(str,"/*-*SCPACK",NULL)) {
   338             while (fgets(str,sizeof str,input)!=NULL)
   339               if (strmatch(str,"/*-*SCPACK",NULL))
   340                 break;
   341           } else {
   342             fprintf(output,"%s",str);
   343           } /* if */
   344         } /* if */
   345       } /* if */
   346       if (strmatch(str,START_TOKEN,NULL))
   347         insection = 1;
   348       if (insection && strmatch(str,"#else",NULL))
   349         break;
   350     } /* while */
   351     if (!strmatch(str,"#else",&indent))
   352       return;           /* no (more) section found, quit */
   353     insection=0;
   354 
   355     /* dump the buffer as strings, separated with commas */
   356     needseparator = 0;
   357     while (*bufptr != '\0') {
   358       assert((unsigned)(bufptr-buffer) < buffersize);
   359       if (needseparator)
   360         fprintf(output, "%s\n",separator);
   361       fprintf(output, "%*c\"",indent+2,' ');
   362       /* loop over string */
   363       while (*bufptr != '\0') {
   364         if (*bufptr<' ' || *bufptr >= 128 || *bufptr == '"' || *bufptr == '\\')
   365           fprintf(output, "\\%03o", *bufptr);
   366         else
   367           fprintf(output, "%c", *bufptr);
   368         bufptr++;
   369       } /* while */
   370       fprintf(output, "\"");
   371       needseparator = 1;
   372       bufptr++;           /* skip '\0' */
   373     } /* while */
   374     fprintf(output, "%s\n",terminator);
   375     bufptr++;
   376 
   377     /* skip the input file until the #endif section */
   378     while (fgets(str,sizeof str,input)!=NULL) {
   379       if (strmatch(str,"#endif",NULL)) {
   380         fprintf(output,"%s",str);
   381         break;          /* done */
   382       } /* if */
   383     } /* while */
   384   } /* while - !feof(input) */
   385 }
   386 
   387 static void usage(void)
   388 {
   389   printf("Usage: scpack <filename> [output file]\n");
   390   exit(1);
   391 }
   392 
   393 int main(int argc, char **argv)
   394 {
   395   FILE *in, *out;
   396   unsigned char *buffer;
   397   unsigned buffersize, orgbuffersize;
   398   unsigned char pairtable[128][2];
   399 
   400   if (argc < 2 || argc > 3)
   401     usage();
   402   if ((in=fopen(argv[1],"rt"))==NULL) {
   403     printf("SCPACK: error opening input %s\n",argv[1]);
   404     usage();
   405   } /* if */
   406   if (argc == 2) {
   407     if ((out=fopen(TEMPFILE,"wt"))==NULL) {
   408       printf("SCPACK: error opening temporary file %s\n",TEMPFILE);
   409       usage();
   410     } /* if */
   411   } else {
   412     if ((out=fopen(argv[2],"wt"))==NULL) {
   413       printf("SCPACK: error opening output file %s\n",argv[2]);
   414       usage();
   415     } /* if */
   416   } /* if */
   417 
   418   buffer = (unsigned char *)malloc(MAXSIZE);
   419   if (buffer == NULL) {
   420     printf("SCPACK: error allocating memory\n");
   421     return 1;
   422   } /* if */
   423   /* 1. read the buffer
   424    * 2. compress the buffer
   425    * 3. copy the file, insert the compressed buffer
   426    */
   427   buffersize = readbuffer(in, buffer);
   428   orgbuffersize = buffersize;
   429   if (buffersize > 0) {
   430     buffersize = compress(buffer, buffersize, pairtable);
   431     writefile(in, out, buffer, buffersize, pairtable);
   432     printf("SCPACK: compression ratio: %ld%% (%d -> %d)\n",
   433            100L-(100L*buffersize)/orgbuffersize, orgbuffersize, buffersize);
   434   } else {
   435     printf("SCPACK: no SCPACK section found, nothing to do\n");
   436   } /* if */
   437   fclose(out);
   438   fclose(in);
   439   /* let the new file replace the old file */
   440   if (buffersize == 0) {
   441     if (argc == 2)
   442       remove(TEMPFILE);
   443     else
   444       remove(argv[2]);
   445   } else if (argc == 2) {
   446     remove(argv[1]);
   447     rename(TEMPFILE,argv[1]);
   448   } /* if */
   449   return 0;
   450 }