1 /* compress.c -- Byte Pair Encoding compression */
2 /* Copyright 1996 Philip Gage */
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).
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.
12 * Compiling instructions:
13 * Borland C++ 16-bit (large memory model is required):
16 * Watcom C/C++ 32-bit:
19 * GNU C (Linux), 32-bit:
20 * gcc scpack.c -o scpack
29 #if UINT_MAX > 0xFFFFU
30 #define MAXSIZE 1024*1024L
32 #define MAXSIZE UINT_MAX /* Input file buffer size */
34 #define HASHSIZE 8192 /* Hash table size, power of 2 */
35 #define THRESHOLD 3 /* Increase for speed, min 3 */
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]="";
46 int compress(unsigned char *buffer, unsigned buffersize, unsigned char pairtable[128][2])
48 unsigned char *left, *right, *count;
49 unsigned char a, b, bestcount;
50 unsigned i, j, index, bestindex, code=128;
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");
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");
68 memset(pairtable, 0, 128*2*sizeof(char));
70 do { /* Replace frequent pairs with bytes 128..255 */
72 /* Enter counts of all byte pairs into hash table */
73 memset(count,0,HASHSIZE);
74 for (i=0; i<buffersize-1; i++) {
77 /* ignore any pair with a '\0' */
80 index = (a ^ (b << 6)) & (HASHSIZE-1);
81 while ((left[index] != a || right[index] != b) &&
83 index = (index + 1) & (HASHSIZE-1);
86 if (count[index] < 255)
87 count[index] += (unsigned char)1;
90 /* Search hash table for most frequent pair */
91 bestcount = THRESHOLD - 1;
92 for (i=0; i<HASHSIZE; i++) {
93 if (count[i] > bestcount) {
99 /* Compress if enough occurrences of pair */
100 if (bestcount >= THRESHOLD) {
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];
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;
113 buffer[j] = buffer[i];
118 } while (++code < 255);
121 free(left); free(right); free(count);
122 return buffersize; /* return adjusted buffersize */
125 static int strmatch(char *str, char *token, int *indent)
129 /* skip whitespace */
130 while (*str==' ' || *str=='\t') {
134 if (strncmp(str,token,strlen(token))!=0)
141 static void check_if(char *str,int linenr)
143 if (strmatch(str,"#if",NULL)) {
144 printf("Error: \"#if...\" preprocessor statement should not be in SCPACK section "
145 "(line %d)\n", linenr);
150 static int check_tablename(char *str)
154 if (strmatch(str,NAME_TOKEN,NULL)) {
155 str += strlen(NAME_TOKEN);
156 while (*str==' ' || *str=='\t')
158 for (i=0; i<(sizeof tablename - 1) && *str!='\0' && strchr(" \t\n",*str)==NULL; i++, str++)
166 static int check_separator(char *str)
170 if (strmatch(str,SEP_TOKEN,NULL)) {
171 str += strlen(SEP_TOKEN);
172 while (*str==' ' || *str=='\t')
174 for (i=0; i<(sizeof separator - 1) && *str!='\0' && strchr(" \t\n",*str)==NULL; i++, str++)
180 if (strmatch(str,TERM_TOKEN,NULL)) {
181 str += strlen(TERM_TOKEN);
182 while (*str==' ' || *str=='\t')
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';
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.
200 unsigned readbuffer(FILE *input, unsigned char *buffer)
210 while (!feof(input)) {
211 while (fgets(str,sizeof str,input)!=NULL) {
213 check_tablename(str);
214 check_separator(str);
215 if (strmatch(str,START_TOKEN,NULL))
218 if (!strmatch(str,START_TOKEN,NULL))
219 return buffersize; /* no (more) section found, quit */
221 while (fgets(str,sizeof str,input)!=NULL) {
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))
229 /* add to the buffer only what is between double quotes */
232 while (str[i]!='\0' && str[i]!='"')
235 /* we are in a string */
237 while (str[i]!='\0' && str[i]!='"') {
238 /* handle escape sequences */
242 case 'a': /* alarm */
243 buffer[buffersize++]='\a';
246 case 'b': /* backspace */
247 buffer[buffersize++]='\b';
250 case 'f': /* form feed */
251 buffer[buffersize++]='\f';
254 case 'n': /* newline */
255 buffer[buffersize++]='\n';
258 case 'r': /* carriage return */
259 buffer[buffersize++]='\n';
263 buffer[buffersize++]='\t';
267 buffer[buffersize++]='\'';
271 buffer[buffersize++]='"';
275 // ??? octal character code escapes and hexadecimal escapes
277 printf("Unknown escape sequence '\\%c' on line %d\n",
281 buffer[buffersize++]=str[i++];
285 buffer[buffersize++]='\0'; /* terminate each string */
288 printf("Error: unterminated string on line %d\n",linenr);
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) */
299 static void write_pairtable(FILE *output, unsigned char pairtable[128][2], char *tablename)
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++) {
308 fprintf(output, "\n ");
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, ",");
316 fprintf(output, "\n};\n");
317 fprintf(output, "/*-*SCPACK end of pair table, do not change or remove this line */\n");
320 void writefile(FILE *input, FILE *output, unsigned char *buffer, unsigned buffersize, unsigned char pairtable[128][2])
323 int insection, indent, needseparator;
324 unsigned char *bufptr;
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))
342 fprintf(output,"%s",str);
346 if (strmatch(str,START_TOKEN,NULL))
348 if (insection && strmatch(str,"#else",NULL))
351 if (!strmatch(str,"#else",&indent))
352 return; /* no (more) section found, quit */
355 /* dump the buffer as strings, separated with commas */
357 while (*bufptr != '\0') {
358 assert((unsigned)(bufptr-buffer) < buffersize);
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);
367 fprintf(output, "%c", *bufptr);
370 fprintf(output, "\"");
372 bufptr++; /* skip '\0' */
374 fprintf(output, "%s\n",terminator);
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);
384 } /* while - !feof(input) */
387 static void usage(void)
389 printf("Usage: scpack <filename> [output file]\n");
393 int main(int argc, char **argv)
396 unsigned char *buffer;
397 unsigned buffersize, orgbuffersize;
398 unsigned char pairtable[128][2];
400 if (argc < 2 || argc > 3)
402 if ((in=fopen(argv[1],"rt"))==NULL) {
403 printf("SCPACK: error opening input %s\n",argv[1]);
407 if ((out=fopen(TEMPFILE,"wt"))==NULL) {
408 printf("SCPACK: error opening temporary file %s\n",TEMPFILE);
412 if ((out=fopen(argv[2],"wt"))==NULL) {
413 printf("SCPACK: error opening output file %s\n",argv[2]);
418 buffer = (unsigned char *)malloc(MAXSIZE);
419 if (buffer == NULL) {
420 printf("SCPACK: error allocating memory\n");
423 /* 1. read the buffer
424 * 2. compress the buffer
425 * 3. copy the file, insert the compressed buffer
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);
435 printf("SCPACK: no SCPACK section found, nothing to do\n");
439 /* let the new file replace the old file */
440 if (buffersize == 0) {
445 } else if (argc == 2) {
447 rename(TEMPFILE,argv[1]);