// Javascript Cruncher
// Copyright (c) 2002 by Charles Foster, cfoster.net
//               (c) 2004 by Andre Demidov, lion.susu.ac.ru/~andre
//
// Removes all unnessacery spaces, tabs, comments etc 
//
// Feel free to use and modify this source and application
// in anyway you like (at your own risk of course) :)

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

const char* _R[] = {"","var ","new ","throw ","typeof ","case ",0};

int js_body=1,					// In main JS execution (no text or comments)
	js_comment_block=0,		// inside /* */
	js_comment=0,					// everything after the // on that line
	js_double=0,					// inside "" - Exceptions: \"
	js_single=0,					// inside '' - Exceptions: \'
	js_regexp=0,					// inside /REGEXP/ - Exceptions: \/
	js_space=0,						// for when a space is needed
	js_else=0,						// after else flag
	js_function=0,				// after function flag
	js_arg=0,							// after break or return flag

	infile_size, outfile_size,
        i,				// misc bits and pieces
	j=0, buf_p=0,					// for main()

	z,y,l,ok;							// misc bits and pieces for isRkey()
// ------
int isRkey(char* b)
{
   z=0;
   while(_R[++z] != '\0')
   {
      ok=1;
      l=strlen(_R[z]);
      for(y=0;y<l;y++)
      {
         if(_R[z][y] != b[y]) { ok=0; break; }
      }
      if(ok) { return 1; }
   }
   return 0;
}
char* t_buffer;
int fcmpstr(const void *a, const void *b)
{
   return strcmp(t_buffer+*(int *)a,t_buffer+*(int *)b);
}
char disabled_chars[100]="(){}[]|.*+-?/'\\^$ \"\x7F\xFF";
char used_chars[256];
char used_pairs[256][256];
char code[3];
int codelen;
char* buffer;
void findcode(void)
{
   int i,j;
   for(i=32;i<256;++i)
      if(!used_chars[i] && strchr(disabled_chars,i)==NULL) {
         codelen=1;
         code[0]=i;
         code[1]=0;
         return;
      }
   for(i=32;i<256;++i)
      if(strchr(disabled_chars,i)==NULL)
         for(j=32;j<256;++j)
            if(strchr(disabled_chars,j)==NULL && !used_pairs[i][j]) {
               codelen=2;
               code[0]=i;
               code[1]=j;
               code[2]=0;
            }
}
int diff(const char *a, const char *b)
{
   int len=0;
   while(*a && *a++==*b++) len++;
   return len;
}
void replacecode(const char *str, int len)
{
   char *p,*t;
   p=t_buffer;
   while((t=strstr(p,str))!=NULL)
   {
      if(len==1) {
        *t=code[0];
        p=t+1;
      }
      else {
        strcpy(t,code);
        strcat(t,t+len);
        p=t+codelen;
      }
   }
}
void markusedchars(void)
{ 
  int i;
  memset(used_pairs,0,sizeof(used_pairs));
  memset(used_chars,0,sizeof(used_chars));
  for(i=0;i<buf_p;++i) {
     used_chars[(unsigned char)t_buffer[i]]=1;
     if(i>0)
        used_pairs[(unsigned char)t_buffer[i-1]][(unsigned char)t_buffer[i]]=1;
  }
}
char nullstr[1000]={0};
int *t_ptr;
int nchar(int ch)
{ 
   int k,i;
   k=0;
   for(i=0;i<buf_p;i++)
      if(t_buffer[i]==ch) 
         k++;
   return k;
}
int calcwin(int i1, int i2, int len)
{
   int i;
   int nrep;
   if(len<3) return 0;
   if(len>1000) len=1000;
   memset(buffer,0,buf_p);
   nrep=0;
   for(i=i1;i<=i2;i++) {
      if(memcmp(buffer+t_ptr[i],nullstr,len)==0) {
         memset(buffer+t_ptr[i],1,len);
         nrep++;
      }
   }
   return len*nrep-(codelen*nrep+codelen+1+len+1);
}
void writestr(const char *s, FILE *f)
{
   while (*s) {
      if(*s=='\\') {
         fputs("\\\\",f);
      }
      else if(*s=='\n') {
         fputs("\\n",f);
      }
      else if(*s=='"') {
         fputs("\\\"",f);
      }
      else
         fputc(*s,f);
      s++;
   }
}
// ------
int main(int args, char** argv)
{
   FILE* infile;
   FILE* outfile;
   int *difflen;
   int *diffpos;
   int ndiff,curlen,maxwin,win;
   char *maxstr,*nosqz_str;
   int maxlen;
   int c,nrep;
   int compression=5;
   static struct {
      char code[3];
      char *str;
   } changes[200];
   int nchanges;
   char divstr[3];
   if(args < 3) {
      printf("Javascript Cruncher 2  (c) 2002 Charles Foster, (c) 2004  Andre Demidov\n"
					 "usage: js infile outfile [1-10]\n"); return 0;
   }
   if(args>3)
   {
     compression=atoi(argv[3]);
     if(compression<1) compression=5;
   }
   if(strcmp(argv[1],argv[2]) == 0) {
      printf("I don't want you to over-write your existing js file\n"); return 0; }
   if((infile = fopen(argv[1],"r")) == 0) {
      printf("%s doesn't exist\n",argv[1]); return 0; }
   if((outfile = fopen(argv[2],"w")) == 0) {
      printf("can't write to %s",argv[2]); return 0; }

   fseek(infile,0,SEEK_END);
   infile_size = ftell(infile);
   rewind(infile);
   buffer = (char*)malloc(infile_size);
   while((c = fgetc(infile)) != EOF) { buffer[buf_p++] = c; }
   t_buffer = (char*)malloc(buf_p+1);
	
   for(i=0;i<buf_p;++i)
   {
      if(js_body)
      {
         if(buffer[i] == '/' && buffer[i+1] == '/') {
            js_body=0; js_comment=1;
         }
         else if(buffer[i] == '/' && buffer[i+1] == '*') {
            js_body=0; js_comment_block=1;
         }
         else if(buffer[i] == '/') {
            t_buffer[j++] = buffer[i];
            js_body=0; js_regexp=1;
         }
         else if(buffer[i] == '"') {
            t_buffer[j++] = buffer[i];
            js_body=0; js_double=1;				
         }
         else if(buffer[i] == '\'') {
            t_buffer[j++] = buffer[i];
            js_body=0; js_single=1;				
         }
         else if(isRkey(&buffer[i])) {
            t_buffer[j++] = buffer[i];
            js_space=1;
         }
         else if(strncmp(&buffer[i],"instanceof",10) == 0) {
            t_buffer[j++] = ' ';
            t_buffer[j++] = buffer[i];
            js_space=1;
         }
         else if(strncmp(&buffer[i],"function",8) == 0) {
            t_buffer[j++] = buffer[i];
            js_space=1;
            js_function=1;
         }
         else if(strncmp(&buffer[i],"else",4) == 0) {
            strncpy(&t_buffer[j],&buffer[i],4);
            i+=3;j+=4;
            js_else=1;
         }
         else if(strncmp(&buffer[i],"return",6) == 0) {
            strncpy(&t_buffer[j],&buffer[i],6);
            i+=5;j+=6;
            js_arg=1;
         }
         else if(strncmp(&buffer[i],"break",5) == 0) {
            strncat(&t_buffer[j],&buffer[i],5);
            i+=4;j+=5;
            js_arg=1;
         }
         else if(buffer[i] != ' ' && buffer[i] != 0x09 && buffer[i] != '\n') {
            if(js_else && (strncmp(&buffer[i],"if",2) == 0 || buffer[i] != '{')) {
               t_buffer[j++] = ' ';
            }
            else if(js_arg && buffer[i] != ';') {
               t_buffer[j++] = ' ';
            }
            else if(js_function && buffer[i] == '(') {
               js_space=0;
            }
            js_else=0;
            js_arg=0;
            t_buffer[j++] = buffer[i];
         }
         else if(buffer[i] == ' ' && js_space && !js_function) {
            t_buffer[j++] = ' ';
            js_space=0;
         }
         else if(buffer[i] == ' ' && js_space && js_function)
         {
            if(buffer[i+1] >= 0x41 && buffer[i+1] <= 0x5A || // A > Z
                     buffer[i+1] >= 0x61 && buffer[i+1] <= 0x7A || // a > z
                     buffer[i+1] == '_')	{
               t_buffer[j++] = buffer[i];
               js_function=0;
               js_space=0;
            }
         }
      } // !js_body:
      else if(js_comment) {
         if(buffer[i] == '\n') {
            js_comment=0;
            js_body=1;
         }
      }
      else if(js_comment_block) {
         if(buffer[i] == '*' && buffer[i+1] == '/') {
            js_comment_block=0;
            js_body=1;
            i++;
         }
      }
      else if(js_regexp) {
         t_buffer[j++] = buffer[i];
         if(buffer[i] == '/' && buffer[i-1] != '\\') {
            js_body=1;
            js_regexp=0;
         }
      }
      else if(js_double) {
         t_buffer[j++] = buffer[i];
         if(buffer[i] == '"' && buffer[i-1] != '\\') {
            js_body=1;
            js_double=0;
         }
      }
      else if(js_single) {
         t_buffer[j++] = buffer[i];
         if(buffer[i] == '\'' && buffer[i-1] != '\\') {
            js_body=1;
            js_single=0;
         }
      }
   }
   t_buffer[buf_p=j]=0;
   nosqz_str=strdup(t_buffer);
   t_ptr = (int *)malloc((buf_p+1)*sizeof(int));
   difflen = (int *)malloc((buf_p+1)*sizeof(int));
   diffpos = (int *)malloc((buf_p+1)*sizeof(int));
   nchanges=0;
   markusedchars();
   findcode();
   strcat(disabled_chars,code);
   strcpy(divstr,code);
   findcode();
   nrep=nchar('"');
   if(codelen==1 && 2*nrep-(nrep+5)>=compression)
   {
      strcpy(changes[nchanges].code,code);
      changes[nchanges].str="\"";
      replacecode(changes[nchanges].str,1);
      nchanges++;
      buf_p=strlen(t_buffer);
   }
   markusedchars();
   findcode();
   nrep=nchar('\\');
   if(codelen==1 && 2*nrep-(nrep+5)>=compression)
   {
      strcpy(changes[nchanges].code,code);
      changes[nchanges].str="\\";
      replacecode(changes[nchanges].str,1);
      nchanges++;
      buf_p=strlen(t_buffer);
   }
   while(nchanges<200) {
      markusedchars();
      for(i=0;i<buf_p;++i) {
         t_ptr[i]=i;
         used_chars[(unsigned char)t_buffer[i]]=1;
         if(i>0)
            used_pairs[(unsigned char)t_buffer[i-1]][(unsigned char)t_buffer[i]]=1;
      }
      t_ptr[buf_p]=buf_p;
      qsort(t_ptr,buf_p,sizeof(char *),fcmpstr);
      findcode();
      // Search best replacement
      ndiff=0;
      diffpos[0]=0;
      difflen[0]=0;
      maxwin=0;
      for(i=1;i<=buf_p;++i) {
         curlen=diff(t_buffer+t_ptr[i-1],t_buffer+t_ptr[i]);
         if(curlen>difflen[ndiff]) {
            diffpos[++ndiff]=i;
            difflen[ndiff]=curlen;
         }
         else if(curlen<difflen[ndiff]) {
            while(curlen<difflen[ndiff]) {
               win=calcwin(diffpos[ndiff]-1,i-1,difflen[ndiff]);
               if(win>maxwin)
               {
                  maxwin=win;
                  maxstr=t_buffer+t_ptr[i-1];
                  maxlen=difflen[ndiff];
               }
               --ndiff;
            }
            if(curlen>difflen[ndiff])
               difflen[++ndiff]=curlen;
         }
      }
      if(maxwin<compression)
         break;
      strcpy(changes[nchanges].code,code);
      changes[nchanges].str=(char *)malloc(maxlen+1);
      strncpy(changes[nchanges].str,maxstr,maxlen);
      changes[nchanges].str[maxlen]=0;
      replacecode(changes[nchanges].str,maxlen);
      nchanges++;
      buf_p=strlen(t_buffer);
   }
   fputs("$=\"",outfile);
   writestr(t_buffer,outfile);
   fputs("\";",outfile);
   if(nchanges>3) {
      fprintf(outfile,"_=\"",nchanges);
      t_buffer[0]=0;
      for(i=nchanges-1;i>=0;i--) {
         strcat(t_buffer,changes[i].code);
         strcat(t_buffer,divstr);
      }
      for(i=nchanges-1;i>=0;i--) {
         strcat(t_buffer,changes[i].str);
         if(i>0)
           strcat(t_buffer,divstr);
      }
      writestr(t_buffer,outfile);
      fprintf(outfile,"\".split('%s');for(I=0;I<%d;I++)$=$.replace(eval('/'+_[I]+'/g'),_[I+%d]);",divstr,nchanges,nchanges);
   }
   else {
      for(i=nchanges-1;i>=0;i--) {
         fprintf(outfile,"$=$.replace(/%s/g,\"",changes[i].code);
         writestr(changes[i].str,outfile);
         fprintf(outfile,"\");",changes[i].code);
      }
   }
   fprintf(outfile,"eval($);");
   outfile_size = ftell(outfile);
   if(strlen(nosqz_str)<outfile_size) {
      fclose(outfile);
      outfile = fopen(argv[2],"w");
      fputs(nosqz_str,outfile);
      outfile_size=strlen(nosqz_str);
   }
   printf("Compress %.1f%%\n",outfile_size*100.0/infile_size);
   fclose(infile);
   fclose(outfile);
   return 0;
}
