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